From 353aa95eba859a6b486d201ae4aae63e5298fb27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20D=C3=A9saulniers?= Date: Mon, 20 Apr 2026 23:58:03 -0400 Subject: [PATCH 1/8] fix(vi): use Vi-standard word boundary rules Replace unicode_segmentation word boundaries with a Vi-compatible three-class system (keyword, punctuation, whitespace) for w/e/b motions. Separate inclusive Vi cut/copy (de/dE/ce/cE/ye/yE) from exclusive Emacs word commands (Alt+d) by adding dedicated EditCommand variants. Closes: #563, #667 Addresses: #788 (point 2) --- src/core_editor/editor.rs | 96 ++++++++++++++- src/core_editor/line_buffer.rs | 218 +++++++++++++++++++++++++++++++++ src/edit_mode/vi/command.rs | 32 +++-- src/edit_mode/vi/motion.rs | 6 +- src/edit_mode/vi/parser.rs | 20 +-- src/enums.rs | 60 +++++++++ 6 files changed, 406 insertions(+), 26 deletions(-) diff --git a/src/core_editor/editor.rs b/src/core_editor/editor.rs index 0d7673ba..6d72686e 100644 --- a/src/core_editor/editor.rs +++ b/src/core_editor/editor.rs @@ -77,6 +77,9 @@ impl Editor { } EditCommand::MoveWordRightEnd { select } => self.move_word_right_end(*select), EditCommand::MoveBigWordRightEnd { select } => self.move_big_word_right_end(*select), + EditCommand::MoveViWordLeft { select } => self.move_vi_word_left(*select), + EditCommand::MoveViWordRightStart { select } => self.move_vi_word_right_start(*select), + EditCommand::MoveViWordRightEnd { select } => self.move_vi_word_right_end(*select), EditCommand::InsertChar(c) => self.insert_char(*c), EditCommand::Complete => {} EditCommand::InsertString(str) => self.insert_str(str), @@ -107,8 +110,11 @@ impl Editor { EditCommand::KillLine => self.kill_line(), EditCommand::CutWordLeft => self.cut_word_left(), EditCommand::CutBigWordLeft => self.cut_big_word_left(), + EditCommand::CutViWordLeft => self.cut_vi_word_left(), EditCommand::CutWordRight => self.cut_word_right(), EditCommand::CutBigWordRight => self.cut_big_word_right(), + EditCommand::CutWordRightEnd => self.cut_word_right_end(), + EditCommand::CutBigWordRightEnd => self.cut_big_word_right_end(), EditCommand::CutWordRightToNext => self.cut_word_right_to_next(), EditCommand::CutBigWordRightToNext => self.cut_big_word_right_to_next(), EditCommand::PasteCutBufferBefore => self.insert_cut_buffer_before(), @@ -150,8 +156,11 @@ impl Editor { EditCommand::CopyToLineEnd => self.copy_to_line_end(), EditCommand::CopyWordLeft => self.copy_word_left(), EditCommand::CopyBigWordLeft => self.copy_big_word_left(), + EditCommand::CopyViWordLeft => self.copy_vi_word_left(), EditCommand::CopyWordRight => self.copy_word_right(), EditCommand::CopyBigWordRight => self.copy_big_word_right(), + EditCommand::CopyWordRightEnd => self.copy_word_right_end(), + EditCommand::CopyBigWordRightEnd => self.copy_big_word_right_end(), EditCommand::CopyWordRightToNext => self.copy_word_right_to_next(), EditCommand::CopyBigWordRightToNext => self.copy_big_word_right_to_next(), EditCommand::CopyRightUntil(c) => self.copy_right_until_char(*c, false, true), @@ -482,6 +491,12 @@ impl Editor { self.cut_range(big_word_start..insertion_offset); } + fn cut_vi_word_left(&mut self) { + let insertion_offset = self.line_buffer.insertion_point(); + let word_start = self.line_buffer.vi_word_left_index(); + self.cut_range(word_start..insertion_offset); + } + fn cut_word_right(&mut self) { let insertion_offset = self.line_buffer.insertion_point(); let word_end = self.line_buffer.word_right_index(); @@ -494,9 +509,27 @@ impl Editor { self.cut_range(insertion_offset..big_word_end); } + /// Cut from cursor to end of next word (inclusive). + /// Used by Vi `de` — the `e` motion is inclusive. + fn cut_word_right_end(&mut self) { + let insertion_offset = self.line_buffer.insertion_point(); + let word_end = self.line_buffer.vi_word_right_end_index(); + let inclusive_end = self.line_buffer.grapheme_right_index_from_pos(word_end); + self.cut_range(insertion_offset..inclusive_end); + } + + /// Cut from cursor to end of next WORD (inclusive). + /// Used by Vi `dE` — the `E` motion is inclusive. + fn cut_big_word_right_end(&mut self) { + let insertion_offset = self.line_buffer.insertion_point(); + let word_end = self.line_buffer.big_word_right_end_index(); + let inclusive_end = self.line_buffer.grapheme_right_index_from_pos(word_end); + self.cut_range(insertion_offset..inclusive_end); + } + fn cut_word_right_to_next(&mut self) { let insertion_offset = self.line_buffer.insertion_point(); - let next_word_start = self.line_buffer.word_right_start_index(); + let next_word_start = self.line_buffer.vi_word_right_start_index(); self.cut_range(insertion_offset..next_word_start); } @@ -763,6 +796,18 @@ impl Editor { self.move_to_position(self.line_buffer.big_word_right_end_index(), select); } + fn move_vi_word_left(&mut self, select: bool) { + self.move_to_position(self.line_buffer.vi_word_left_index(), select); + } + + fn move_vi_word_right_start(&mut self, select: bool) { + self.move_to_position(self.line_buffer.vi_word_right_start_index(), select); + } + + fn move_vi_word_right_end(&mut self, select: bool) { + self.move_to_position(self.line_buffer.vi_word_right_end_index(), select); + } + fn insert_char(&mut self, c: char) { self.delete_selection(); self.line_buffer.insert_char(c); @@ -1038,6 +1083,12 @@ impl Editor { self.copy_range(big_word_start..insertion_offset); } + pub(crate) fn copy_vi_word_left(&mut self) { + let insertion_offset = self.line_buffer.insertion_point(); + let word_start = self.line_buffer.vi_word_left_index(); + self.copy_range(word_start..insertion_offset); + } + pub(crate) fn copy_word_right(&mut self) { let insertion_offset = self.line_buffer.insertion_point(); let word_end = self.line_buffer.word_right_index(); @@ -1050,9 +1101,27 @@ impl Editor { self.copy_range(insertion_offset..big_word_end); } + /// Copy from cursor to end of next word (inclusive). + /// Used by Vi `ye` — the `e` motion is inclusive. + pub(crate) fn copy_word_right_end(&mut self) { + let insertion_offset = self.line_buffer.insertion_point(); + let word_end = self.line_buffer.vi_word_right_end_index(); + let inclusive_end = self.line_buffer.grapheme_right_index_from_pos(word_end); + self.copy_range(insertion_offset..inclusive_end); + } + + /// Copy from cursor to end of next WORD (inclusive). + /// Used by Vi `yE` — the `E` motion is inclusive. + pub(crate) fn copy_big_word_right_end(&mut self) { + let insertion_offset = self.line_buffer.insertion_point(); + let word_end = self.line_buffer.big_word_right_end_index(); + let inclusive_end = self.line_buffer.grapheme_right_index_from_pos(word_end); + self.copy_range(insertion_offset..inclusive_end); + } + pub(crate) fn copy_word_right_to_next(&mut self) { let insertion_offset = self.line_buffer.insertion_point(); - let next_word_start = self.line_buffer.word_right_start_index(); + let next_word_start = self.line_buffer.vi_word_right_start_index(); self.copy_range(insertion_offset..next_word_start); } @@ -1192,6 +1261,29 @@ mod test { assert_eq!(editor.get_buffer(), expected); } + #[rstest] + // de from start of "abc def": cuts "abc" (inclusive end of word) + #[case("abc def", 0, "abc", " def")] + // de on 'b' in "a.b.c": cuts "b." (word 'b' end is 'b', next word '.' + // end is '.', so inclusive range covers "b.") + #[case("a.b.c", 2, "b.", "a.c")] + // de on 'a' in "abc.def": cuts "abc" (end of word 'abc') + #[case("abc.def", 0, "abc", ".def")] + fn test_cut_word_right_end( + #[case] input: &str, + #[case] position: usize, + #[case] expected_cut: &str, + #[case] expected_buffer: &str, + ) { + let mut editor = editor_with(input); + editor.line_buffer.set_insertion_point(position); + + editor.cut_word_right_end(); + + assert_eq!(editor.get_buffer(), expected_buffer); + assert_eq!(editor.cut_buffer.get().0, expected_cut); + } + #[rstest] #[case("hello world", 0, 'l', 1, false, "lo world")] #[case("hello world", 0, 'l', 1, true, "llo world")] diff --git a/src/core_editor/line_buffer.rs b/src/core_editor/line_buffer.rs index 416e13fe..c22ca057 100644 --- a/src/core_editor/line_buffer.rs +++ b/src/core_editor/line_buffer.rs @@ -199,6 +199,14 @@ impl LineBuffer { .unwrap_or_else(|| self.lines.len()) } + /// Cursor position *behind* the next Vi word to the right + pub fn vi_word_right_index(&self) -> usize { + vi_word_segments(&self.lines[self.insertion_point..]) + .find(|(_, word)| !is_whitespace_str(word)) + .map(|(i, word)| self.insertion_point + i + word.len()) + .unwrap_or_else(|| self.lines.len()) + } + /// Cursor position *behind* the next WORD to the right pub fn big_word_right_index(&self) -> usize { let mut found_ws = false; @@ -232,6 +240,24 @@ impl LineBuffer { }) } + /// Cursor position *at end of* the next Vi word to the right + pub fn vi_word_right_end_index(&self) -> usize { + vi_word_segments(&self.lines[self.insertion_point..]) + .find_map(|(i, word)| { + word.grapheme_indices(true) + .next_back() + .map(|x| self.insertion_point + x.0 + i) + .filter(|x| !is_whitespace_str(word) && *x != self.insertion_point) + }) + .unwrap_or_else(|| { + self.lines + .grapheme_indices(true) + .next_back() + .map(|x| x.0) + .unwrap_or(0) + }) + } + /// Cursor position *at end of* the next WORD to the right pub fn big_word_right_end_index(&self) -> usize { self.lines[self.insertion_point..] @@ -266,6 +292,14 @@ impl LineBuffer { .unwrap_or_else(|| self.lines.len()) } + /// Cursor position *in front of* the next Vi word to the right + pub fn vi_word_right_start_index(&self) -> usize { + vi_word_segments(&self.lines[self.insertion_point..]) + .find(|(i, word)| *i != 0 && !is_whitespace_str(word)) + .map(|(i, _)| self.insertion_point + i) + .unwrap_or_else(|| self.lines.len()) + } + /// Cursor position *in front of* the next WORD to the right pub fn big_word_right_start_index(&self) -> usize { let mut found_ws = false; @@ -289,6 +323,15 @@ impl LineBuffer { .unwrap_or(0) } + /// Cursor position *in front of* the next Vi word to the left + pub fn vi_word_left_index(&self) -> usize { + vi_word_segments(&self.lines[..self.insertion_point]) + .filter(|(_, word)| !is_whitespace_str(word)) + .last() + .map(|(i, _)| i) + .unwrap_or(0) + } + /// Cursor position *in front of* the next WORD to the left pub fn big_word_left_index(&self) -> usize { self.lines[..self.insertion_point] @@ -529,6 +572,18 @@ impl LineBuffer { left_index..right_index } + /// Gets the range of the Vi word the current edit position is pointing to + pub fn vi_current_word_range(&self) -> Range { + let right_index = self.vi_word_right_index(); + let left_index = vi_word_segments(&self.lines[..right_index]) + .filter(|(_, word)| !is_whitespace_str(word)) + .last() + .map(|(i, _)| i) + .unwrap_or(0); + + left_index..right_index + } + /// Range over the current line /// /// Starts on the first non-newline character and is an exclusive range @@ -1065,6 +1120,56 @@ impl LineBuffer { } } +/// Character class for Vi-style word motions (w/e/b). +/// +/// Vi defines three classes of characters: +/// - **Keyword**: letters, digits, and underscores +/// - **Punctuation**: non-blank, non-keyword characters +/// - **Whitespace**: spaces, tabs, and other blanks +/// +/// A *word* is a maximal sequence of keyword characters OR a maximal sequence +/// of punctuation characters. Transitions between classes are word boundaries. +/// +/// Reference: Vim's `:help word`, GNU Readline's `_rl_isident()`, POSIX vi. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum ViCharClass { + Keyword, + Punctuation, + Whitespace, +} + +impl ViCharClass { + fn of(c: char) -> Self { + if c.is_alphanumeric() || c == '_' { + Self::Keyword + } else if c.is_whitespace() { + Self::Whitespace + } else { + Self::Punctuation + } + } +} + +/// Iterate over Vi-style word segments in a string. +/// +/// Each yielded item is `(byte_offset, substring)` where all characters in +/// `substring` belong to the same [`ViCharClass`]. +fn vi_word_segments(s: &str) -> impl Iterator { + let mut chars = s.char_indices().peekable(); + std::iter::from_fn(move || { + let (first_idx, first_char) = chars.next()?; + let class = ViCharClass::of(first_char); + while let Some(&(_, next_char)) = chars.peek() { + if ViCharClass::of(next_char) != class { + break; + } + chars.next(); + } + let end = chars.peek().map_or(s.len(), |&(i, _)| i); + Some((first_idx, &s[first_idx..end])) + }) +} + /// Match any sequence of characters that are considered a word boundary fn is_whitespace_str(s: &str) -> bool { s.chars().all(char::is_whitespace) @@ -1907,6 +2012,119 @@ mod test { assert_eq!(index, expected); } + // ── Vi word segmentation tests ────────────────────────────────────── + + #[test] + fn test_vi_word_segments_basic() { + let segs: Vec<_> = vi_word_segments("abc def").collect(); + assert_eq!(segs, vec![(0, "abc"), (3, " "), (4, "def")]); + } + + #[test] + fn test_vi_word_segments_punctuation() { + // a.b.c → 5 segments: a, ., b, ., c + let segs: Vec<_> = vi_word_segments("a.b.c").collect(); + assert_eq!(segs, vec![(0, "a"), (1, "."), (2, "b"), (3, "."), (4, "c")]); + } + + #[test] + fn test_vi_word_segments_consecutive_punct() { + // "..." is a single punctuation word + let segs: Vec<_> = vi_word_segments("...").collect(); + assert_eq!(segs, vec![(0, "...")]); + } + + #[test] + fn test_vi_word_segments_mixed() { + let segs: Vec<_> = vi_word_segments("foo_bar.baz").collect(); + assert_eq!(segs, vec![(0, "foo_bar"), (7, "."), (8, "baz")]); + } + + #[test] + fn test_vi_word_segments_colons() { + let segs: Vec<_> = vi_word_segments("abc:def").collect(); + assert_eq!(segs, vec![(0, "abc"), (3, ":"), (4, "def")]); + } + + #[test] + fn test_vi_word_segments_mixed_punct() { + let segs: Vec<_> = vi_word_segments("abc...def").collect(); + assert_eq!(segs, vec![(0, "abc"), (3, "..."), (6, "def")]); + } + + #[test] + fn test_vi_word_segments_empty() { + let segs: Vec<_> = vi_word_segments("").collect(); + assert_eq!(segs, Vec::<(usize, &str)>::new()); + } + + // ── Additional Vi word motion tests (punctuation) ─────────────────── + + #[rstest] + // a.b.c: w from 'a' → position 1 (the dot) + #[case("a.b.c", 0, 1)] + // abc:def: w from 'a' → position 3 (the colon) + #[case("abc:def", 0, 3)] + // abc...def: w from 'a' → position 3 (first dot) + #[case("abc...def", 0, 3)] + // foo_bar.baz: w from 'f' → position 7 (the dot) + #[case("foo_bar.baz", 0, 7)] + // a.b.c: w from position 1 (dot) → position 2 ('b') + #[case("a.b.c", 1, 2)] + fn test_word_right_start_index_punct( + #[case] input: &str, + #[case] position: usize, + #[case] expected: usize, + ) { + let mut line_buffer = buffer_with(input); + line_buffer.set_insertion_point(position); + assert_eq!(line_buffer.vi_word_right_start_index(), expected); + } + + #[rstest] + // a.b.c: e from 'a' → position 1 (the dot, next non-ws word end) + #[case("a.b.c", 0, 1)] + // abc:def: e from 'a' → position 2 (end of 'abc') + #[case("abc:def", 0, 2)] + // abc...def: e from 'a' → position 2 (end of 'abc') + #[case("abc...def", 0, 2)] + // foo_bar.baz: e from 'f' → position 6 (end of 'foo_bar') + #[case("foo_bar.baz", 0, 6)] + // a.b.c: e from 'b' (pos 2) → position 3 (the dot after b) + #[case("a.b.c", 2, 3)] + fn test_word_right_end_index_punct( + #[case] input: &str, + #[case] position: usize, + #[case] expected: usize, + ) { + let mut line_buffer = buffer_with(input); + line_buffer.set_insertion_point(position); + assert_eq!(line_buffer.vi_word_right_end_index(), expected); + } + + #[rstest] + // a.b.c: b from 'c' (pos 4) → position 3 (the second dot) + #[case("a.b.c", 4, 3)] + // abc:def: b from end (pos 7) → position 4 ('d') + #[case("abc:def", 7, 4)] + // abc:def: b from 'd' (pos 4) → position 3 (':') + #[case("abc:def", 4, 3)] + // abc...def: b from end (pos 9) → position 6 ('d') + #[case("abc...def", 9, 6)] + // foo_bar.baz: b from end (pos 11) → position 8 ('b') + #[case("foo_bar.baz", 11, 8)] + // a.b.c.d: b from '.' between c and d (pos 5) → position 4 ('c') + #[case("a.b.c.d", 5, 4)] + fn test_word_left_index_punct( + #[case] input: &str, + #[case] position: usize, + #[case] expected: usize, + ) { + let mut line_buffer = buffer_with(input); + line_buffer.set_insertion_point(position); + assert_eq!(line_buffer.vi_word_left_index(), expected); + } + #[rstest] #[case("abc def", 0, 3)] #[case("abc def ghi", 3, 7)] diff --git a/src/edit_mode/vi/command.rs b/src/edit_mode/vi/command.rs index 18999e5c..d77df0e7 100644 --- a/src/edit_mode/vi/command.rs +++ b/src/edit_mode/vi/command.rs @@ -335,11 +335,15 @@ impl Command { Motion::NextBigWord => Some(vec![ReedlineOption::Edit( EditCommand::CutBigWordRightToNext, )]), - Motion::NextWordEnd => Some(vec![ReedlineOption::Edit(EditCommand::CutWordRight)]), + Motion::NextWordEnd => { + Some(vec![ReedlineOption::Edit(EditCommand::CutWordRightEnd)]) + } Motion::NextBigWordEnd => { - Some(vec![ReedlineOption::Edit(EditCommand::CutBigWordRight)]) + Some(vec![ReedlineOption::Edit(EditCommand::CutBigWordRightEnd)]) + } + Motion::PreviousWord => { + Some(vec![ReedlineOption::Edit(EditCommand::CutViWordLeft)]) } - Motion::PreviousWord => Some(vec![ReedlineOption::Edit(EditCommand::CutWordLeft)]), Motion::PreviousBigWord => { Some(vec![ReedlineOption::Edit(EditCommand::CutBigWordLeft)]) } @@ -393,18 +397,20 @@ impl Command { ReedlineOption::Edit(EditCommand::MoveToLineStart { select: false }), ReedlineOption::Edit(EditCommand::CutToLineEnd), ]), - Motion::NextWord => Some(vec![ReedlineOption::Edit(EditCommand::CutWordRight)]), + Motion::NextWord => { + Some(vec![ReedlineOption::Edit(EditCommand::CutWordRightEnd)]) + } Motion::NextBigWord => { - Some(vec![ReedlineOption::Edit(EditCommand::CutBigWordRight)]) + Some(vec![ReedlineOption::Edit(EditCommand::CutBigWordRightEnd)]) } Motion::NextWordEnd => { - Some(vec![ReedlineOption::Edit(EditCommand::CutWordRight)]) + Some(vec![ReedlineOption::Edit(EditCommand::CutWordRightEnd)]) } Motion::NextBigWordEnd => { - Some(vec![ReedlineOption::Edit(EditCommand::CutBigWordRight)]) + Some(vec![ReedlineOption::Edit(EditCommand::CutBigWordRightEnd)]) } Motion::PreviousWord => { - Some(vec![ReedlineOption::Edit(EditCommand::CutWordLeft)]) + Some(vec![ReedlineOption::Edit(EditCommand::CutViWordLeft)]) } Motion::PreviousBigWord => { Some(vec![ReedlineOption::Edit(EditCommand::CutBigWordLeft)]) @@ -470,11 +476,15 @@ impl Command { Motion::NextBigWord => Some(vec![ReedlineOption::Edit( EditCommand::CopyBigWordRightToNext, )]), - Motion::NextWordEnd => Some(vec![ReedlineOption::Edit(EditCommand::CopyWordRight)]), + Motion::NextWordEnd => { + Some(vec![ReedlineOption::Edit(EditCommand::CopyWordRightEnd)]) + } Motion::NextBigWordEnd => { - Some(vec![ReedlineOption::Edit(EditCommand::CopyBigWordRight)]) + Some(vec![ReedlineOption::Edit(EditCommand::CopyBigWordRightEnd)]) + } + Motion::PreviousWord => { + Some(vec![ReedlineOption::Edit(EditCommand::CopyViWordLeft)]) } - Motion::PreviousWord => Some(vec![ReedlineOption::Edit(EditCommand::CopyWordLeft)]), Motion::PreviousBigWord => { Some(vec![ReedlineOption::Edit(EditCommand::CopyBigWordLeft)]) } diff --git a/src/edit_mode/vi/motion.rs b/src/edit_mode/vi/motion.rs index 6fac6ec7..d525cc57 100644 --- a/src/edit_mode/vi/motion.rs +++ b/src/edit_mode/vi/motion.rs @@ -195,13 +195,13 @@ impl Motion { ReedlineEvent::Down, ])) }], - Motion::NextWord => vec![ReedlineOption::Edit(EditCommand::MoveWordRightStart { + Motion::NextWord => vec![ReedlineOption::Edit(EditCommand::MoveViWordRightStart { select: select_mode, })], Motion::NextBigWord => vec![ReedlineOption::Edit(EditCommand::MoveBigWordRightStart { select: select_mode, })], - Motion::NextWordEnd => vec![ReedlineOption::Edit(EditCommand::MoveWordRightEnd { + Motion::NextWordEnd => vec![ReedlineOption::Edit(EditCommand::MoveViWordRightEnd { select: select_mode, })], Motion::NextBigWordEnd => { @@ -209,7 +209,7 @@ impl Motion { select: select_mode, })] } - Motion::PreviousWord => vec![ReedlineOption::Edit(EditCommand::MoveWordLeft { + Motion::PreviousWord => vec![ReedlineOption::Edit(EditCommand::MoveViWordLeft { select: select_mode, })], Motion::PreviousBigWord => vec![ReedlineOption::Edit(EditCommand::MoveBigWordLeft { diff --git a/src/edit_mode/vi/parser.rs b/src/edit_mode/vi/parser.rs index da0839cd..d860c075 100644 --- a/src/edit_mode/vi/parser.rs +++ b/src/edit_mode/vi/parser.rs @@ -491,7 +491,7 @@ mod tests { ReedlineEvent::Up, ])]))] #[case(&['w'], - ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::MoveWordRightStart{select:false}])]))] + ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::MoveViWordRightStart{select:false}])]))] #[case(&['W'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::MoveBigWordRightStart{select:false}])]))] #[case(&['2', 'l'], ReedlineEvent::Multiple(vec![ @@ -526,22 +526,22 @@ mod tests { ReedlineEvent::Edit(vec![EditCommand::CutCurrentLine])]))] #[case(&['d', 'w'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutWordRightToNext])]))] #[case(&['d', 'W'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutBigWordRightToNext])]))] - #[case(&['d', 'e'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutWordRight])]))] - #[case(&['d', 'b'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutWordLeft])]))] + #[case(&['d', 'e'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutWordRightEnd])]))] + #[case(&['d', 'b'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutViWordLeft])]))] #[case(&['d', 'B'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutBigWordLeft])]))] #[case(&['c', 'c'], ReedlineEvent::Multiple(vec![ ReedlineEvent::Edit(vec![EditCommand::MoveToLineStart { select: false }]), ReedlineEvent::Edit(vec![EditCommand::CutToLineEnd]), ReedlineEvent::Repaint]))] - #[case(&['c', 'w'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutWordRight]), ReedlineEvent::Repaint]))] - #[case(&['c', 'W'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutBigWordRight]), ReedlineEvent::Repaint]))] - #[case(&['c', 'e'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutWordRight]), ReedlineEvent::Repaint]))] - #[case(&['c', 'b'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutWordLeft]), ReedlineEvent::Repaint]))] + #[case(&['c', 'w'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutWordRightEnd]), ReedlineEvent::Repaint]))] + #[case(&['c', 'W'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutBigWordRightEnd]), ReedlineEvent::Repaint]))] + #[case(&['c', 'e'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutWordRightEnd]), ReedlineEvent::Repaint]))] + #[case(&['c', 'b'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutViWordLeft]), ReedlineEvent::Repaint]))] #[case(&['c', 'B'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutBigWordLeft]), ReedlineEvent::Repaint]))] #[case(&['d', 'h'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::Backspace])]))] #[case(&['d', 'l'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::Delete])]))] #[case(&['2', 'd', 'd'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutCurrentLine]), ReedlineEvent::Edit(vec![EditCommand::CutCurrentLine])]))] // #[case(&['d', 'j'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutCurrentLine]), ReedlineEvent::Edit(vec![EditCommand::CutCurrentLine])]))] // #[case(&['d', 'k'], ReedlineEvent::Multiple(vec![ReedlineEvent::Up, ReedlineEvent::Edit(vec![EditCommand::CutCurrentLine]), ReedlineEvent::Edit(vec![EditCommand::CutCurrentLine])]))] - #[case(&['d', 'E'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutBigWordRight])]))] + #[case(&['d', 'E'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutBigWordRightEnd])]))] #[case(&['d', '0'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutFromLineStart])]))] #[case(&['d', '^'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutFromLineNonBlankStart])]))] #[case(&['d', '$'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutToLineEnd])]))] @@ -551,7 +551,7 @@ mod tests { #[case(&['d', 'T', 'a'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutLeftBefore('a')])]))] #[case(&['d', 'g', 'g'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutFromStartLinewise { leave_blank_line: false }])]))] #[case(&['d', 'G'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutToEndLinewise { leave_blank_line: false }])]))] - #[case(&['c', 'E'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutBigWordRight]), ReedlineEvent::Repaint]))] + #[case(&['c', 'E'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutBigWordRightEnd]), ReedlineEvent::Repaint]))] #[case(&['c', '0'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutFromLineStart]), ReedlineEvent::Repaint]))] #[case(&['c', '^'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutFromLineNonBlankStart]), ReedlineEvent::Repaint]))] #[case(&['c', '$'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutToLineEnd]), ReedlineEvent::Repaint]))] @@ -622,7 +622,7 @@ mod tests { ])]))] #[case(&['k'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::MoveLineUp { select: true }])]))] #[case(&['w'], - ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::MoveWordRightStart{select:true}])]))] + ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::MoveViWordRightStart{select:true}])]))] #[case(&['W'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::MoveBigWordRightStart{select:true}])]))] #[case(&['2', 'l'], ReedlineEvent::Multiple(vec![ diff --git a/src/enums.rs b/src/enums.rs index 46228e0b..b7bcdb92 100644 --- a/src/enums.rs +++ b/src/enums.rs @@ -194,6 +194,24 @@ pub enum EditCommand { select: bool, }, + /// Move one Vi word to the left (uses Vi three-class word boundaries) + MoveViWordLeft { + /// Select the text between the current cursor position and destination + select: bool, + }, + + /// Move to the start of the next Vi word (uses Vi three-class word boundaries) + MoveViWordRightStart { + /// Select the text between the current cursor position and destination + select: bool, + }, + + /// Move to the end of the next Vi word (uses Vi three-class word boundaries) + MoveViWordRightEnd { + /// Select the text between the current cursor position and destination + select: bool, + }, + /// Move to position MoveToPosition { /// Position to move to @@ -302,12 +320,21 @@ pub enum EditCommand { /// Cut the word right of the insertion point CutBigWordRight, + /// Cut from the insertion point to the end of the next word (inclusive, for Vi `de`) + CutWordRightEnd, + + /// Cut from the insertion point to the end of the next WORD (inclusive, for Vi `dE`) + CutBigWordRightEnd, + /// Cut the word right of the insertion point and any following space CutWordRightToNext, /// Cut the WORD right of the insertion point and any following space CutBigWordRightToNext, + /// Cut the Vi word left of the insertion point (uses Vi three-class word boundaries) + CutViWordLeft, + /// Paste the cut buffer in front of the insertion point (Emacs, vi `P`) PasteCutBufferBefore, @@ -424,12 +451,21 @@ pub enum EditCommand { /// Copy the WORD left of the insertion point CopyBigWordLeft, + /// Copy the Vi word left of the insertion point (uses Vi three-class word boundaries) + CopyViWordLeft, + /// Copy the word right of the insertion point CopyWordRight, /// Copy the WORD right of the insertion point CopyBigWordRight, + /// Copy from the insertion point to the end of the next word (inclusive, for Vi `ye`) + CopyWordRightEnd, + + /// Copy from the insertion point to the end of the next WORD (inclusive, for Vi `yE`) + CopyBigWordRightEnd, + /// Copy the word right of the insertion point and any following space CopyWordRightToNext, @@ -545,6 +581,15 @@ impl Display for EditCommand { EditCommand::MoveBigWordRightEnd { .. } => { write!(f, "MoveBigWordRightEnd Optional[select: ]") } + EditCommand::MoveViWordLeft { .. } => { + write!(f, "MoveViWordLeft Optional[select: ]") + } + EditCommand::MoveViWordRightStart { .. } => { + write!(f, "MoveViWordRightStart Optional[select: ]") + } + EditCommand::MoveViWordRightEnd { .. } => { + write!(f, "MoveViWordRightEnd Optional[select: ]") + } EditCommand::MoveWordRightStart { .. } => { write!(f, "MoveWordRightStart Optional[select: ]") } @@ -592,8 +637,11 @@ impl Display for EditCommand { EditCommand::CutBigWordLeft => write!(f, "CutBigWordLeft"), EditCommand::CutWordRight => write!(f, "CutWordRight"), EditCommand::CutBigWordRight => write!(f, "CutBigWordRight"), + EditCommand::CutWordRightEnd => write!(f, "CutWordRightEnd"), + EditCommand::CutBigWordRightEnd => write!(f, "CutBigWordRightEnd"), EditCommand::CutWordRightToNext => write!(f, "CutWordRightToNext"), EditCommand::CutBigWordRightToNext => write!(f, "CutBigWordRightToNext"), + EditCommand::CutViWordLeft => write!(f, "CutViWordLeft"), EditCommand::PasteCutBufferBefore => write!(f, "PasteCutBufferBefore"), EditCommand::PasteCutBufferAfter => write!(f, "PasteCutBufferAfter"), EditCommand::UppercaseWord => write!(f, "UppercaseWord"), @@ -624,8 +672,11 @@ impl Display for EditCommand { EditCommand::CopyCurrentLine => write!(f, "CopyCurrentLine"), EditCommand::CopyWordLeft => write!(f, "CopyWordLeft"), EditCommand::CopyBigWordLeft => write!(f, "CopyBigWordLeft"), + EditCommand::CopyViWordLeft => write!(f, "CopyViWordLeft"), EditCommand::CopyWordRight => write!(f, "CopyWordRight"), EditCommand::CopyBigWordRight => write!(f, "CopyBigWordRight"), + EditCommand::CopyWordRightEnd => write!(f, "CopyWordRightEnd"), + EditCommand::CopyBigWordRightEnd => write!(f, "CopyBigWordRightEnd"), EditCommand::CopyWordRightToNext => write!(f, "CopyWordRightToNext"), EditCommand::CopyBigWordRightToNext => write!(f, "CopyBigWordRightToNext"), EditCommand::CopyLeft => write!(f, "CopyLeft"), @@ -674,6 +725,9 @@ impl EditCommand { | EditCommand::MoveBigWordRightStart { select, .. } | EditCommand::MoveWordRightEnd { select, .. } | EditCommand::MoveBigWordRightEnd { select, .. } + | EditCommand::MoveViWordLeft { select, .. } + | EditCommand::MoveViWordRightStart { select, .. } + | EditCommand::MoveViWordRightEnd { select, .. } | EditCommand::MoveRightUntil { select, .. } | EditCommand::MoveRightBefore { select, .. } | EditCommand::MoveLeftUntil { select, .. } @@ -712,8 +766,11 @@ impl EditCommand { | EditCommand::CutBigWordLeft | EditCommand::CutWordRight | EditCommand::CutBigWordRight + | EditCommand::CutWordRightEnd + | EditCommand::CutBigWordRightEnd | EditCommand::CutWordRightToNext | EditCommand::CutBigWordRightToNext + | EditCommand::CutViWordLeft | EditCommand::PasteCutBufferBefore | EditCommand::PasteCutBufferAfter | EditCommand::UppercaseWord @@ -750,8 +807,11 @@ impl EditCommand { | EditCommand::CopyCurrentLine | EditCommand::CopyWordLeft | EditCommand::CopyBigWordLeft + | EditCommand::CopyViWordLeft | EditCommand::CopyWordRight | EditCommand::CopyBigWordRight + | EditCommand::CopyWordRightEnd + | EditCommand::CopyBigWordRightEnd | EditCommand::CopyWordRightToNext | EditCommand::CopyBigWordRightToNext | EditCommand::CopyLeft From 315dd8613c08804d9d35134246c087d96398662c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20D=C3=A9saulniers?= Date: Tue, 21 Apr 2026 00:36:26 -0400 Subject: [PATCH 2/8] refactor: deprecate ambiguous word_* functions in favor of emacs_* aliases Add emacs_word_right_index, emacs_word_right_end_index, emacs_word_right_start_index, emacs_word_left_index, and emacs_current_word_range as the canonical Emacs-path functions. Deprecate the unprefixed word_* variants to encourage callers to choose explicitly between emacs_* and vi_* word boundaries. All internal callers migrated to emacs_* variants. --- src/core_editor/editor.rs | 18 +++---- src/core_editor/line_buffer.rs | 86 ++++++++++++++++++++++++++++------ 2 files changed, 81 insertions(+), 23 deletions(-) diff --git a/src/core_editor/editor.rs b/src/core_editor/editor.rs index 6d72686e..2c432664 100644 --- a/src/core_editor/editor.rs +++ b/src/core_editor/editor.rs @@ -481,7 +481,7 @@ impl Editor { fn cut_word_left(&mut self) { let insertion_offset = self.line_buffer.insertion_point(); - let word_start = self.line_buffer.word_left_index(); + let word_start = self.line_buffer.emacs_word_left_index(); self.cut_range(word_start..insertion_offset); } @@ -499,7 +499,7 @@ impl Editor { fn cut_word_right(&mut self) { let insertion_offset = self.line_buffer.insertion_point(); - let word_end = self.line_buffer.word_right_index(); + let word_end = self.line_buffer.emacs_word_right_index(); self.cut_range(insertion_offset..word_end); } @@ -769,7 +769,7 @@ impl Editor { } fn move_word_left(&mut self, select: bool) { - self.move_to_position(self.line_buffer.word_left_index(), select); + self.move_to_position(self.line_buffer.emacs_word_left_index(), select); } fn move_big_word_left(&mut self, select: bool) { @@ -777,11 +777,11 @@ impl Editor { } fn move_word_right(&mut self, select: bool) { - self.move_to_position(self.line_buffer.word_right_index(), select); + self.move_to_position(self.line_buffer.emacs_word_right_index(), select); } fn move_word_right_start(&mut self, select: bool) { - self.move_to_position(self.line_buffer.word_right_start_index(), select); + self.move_to_position(self.line_buffer.emacs_word_right_start_index(), select); } fn move_big_word_right_start(&mut self, select: bool) { @@ -789,7 +789,7 @@ impl Editor { } fn move_word_right_end(&mut self, select: bool) { - self.move_to_position(self.line_buffer.word_right_end_index(), select); + self.move_to_position(self.line_buffer.emacs_word_right_end_index(), select); } fn move_big_word_right_end(&mut self, select: bool) { @@ -890,7 +890,7 @@ impl Editor { self.line_buffer .current_whitespace_range() .unwrap_or_else(|| { - let word_range = self.line_buffer.current_word_range(); + let word_range = self.line_buffer.emacs_current_word_range(); match text_object_scope { TextObjectScope::Inner => word_range, TextObjectScope::Around => { @@ -1073,7 +1073,7 @@ impl Editor { pub(crate) fn copy_word_left(&mut self) { let insertion_offset = self.line_buffer.insertion_point(); - let word_start = self.line_buffer.word_left_index(); + let word_start = self.line_buffer.emacs_word_left_index(); self.copy_range(word_start..insertion_offset); } @@ -1091,7 +1091,7 @@ impl Editor { pub(crate) fn copy_word_right(&mut self) { let insertion_offset = self.line_buffer.insertion_point(); - let word_end = self.line_buffer.word_right_index(); + let word_end = self.line_buffer.emacs_word_right_index(); self.copy_range(insertion_offset..word_end); } diff --git a/src/core_editor/line_buffer.rs b/src/core_editor/line_buffer.rs index c22ca057..a1733011 100644 --- a/src/core_editor/line_buffer.rs +++ b/src/core_editor/line_buffer.rs @@ -191,7 +191,18 @@ impl LineBuffer { } /// Cursor position *behind* the next word to the right + /// + /// Uses Unicode UAX #29 word boundaries. For Vi-style word boundaries, + /// use [`vi_word_right_index`](Self::vi_word_right_index). + #[deprecated(note = "use emacs_word_right_index or vi_word_right_index for clarity")] pub fn word_right_index(&self) -> usize { + self.emacs_word_right_index() + } + + /// Cursor position *behind* the next Emacs word to the right + /// + /// Uses Unicode UAX #29 word boundaries. + pub fn emacs_word_right_index(&self) -> usize { self.lines[self.insertion_point..] .split_word_bound_indices() .find(|(_, word)| !is_whitespace_str(word)) @@ -222,7 +233,18 @@ impl LineBuffer { } /// Cursor position *at end of* the next word to the right + /// + /// Uses Unicode UAX #29 word boundaries. For Vi-style word boundaries, + /// use [`vi_word_right_end_index`](Self::vi_word_right_end_index). + #[deprecated(note = "use emacs_word_right_end_index or vi_word_right_end_index for clarity")] pub fn word_right_end_index(&self) -> usize { + self.emacs_word_right_end_index() + } + + /// Cursor position *at end of* the next Emacs word to the right + /// + /// Uses Unicode UAX #29 word boundaries. + pub fn emacs_word_right_end_index(&self) -> usize { self.lines[self.insertion_point..] .split_word_bound_indices() .find_map(|(i, word)| { @@ -284,7 +306,20 @@ impl LineBuffer { } /// Cursor position *in front of* the next word to the right + /// + /// Uses Unicode UAX #29 word boundaries. For Vi-style word boundaries, + /// use [`vi_word_right_start_index`](Self::vi_word_right_start_index). + #[deprecated( + note = "use emacs_word_right_start_index or vi_word_right_start_index for clarity" + )] pub fn word_right_start_index(&self) -> usize { + self.emacs_word_right_start_index() + } + + /// Cursor position *in front of* the next Emacs word to the right + /// + /// Uses Unicode UAX #29 word boundaries. + pub fn emacs_word_right_start_index(&self) -> usize { self.lines[self.insertion_point..] .split_word_bound_indices() .find(|(i, word)| *i != 0 && !is_whitespace_str(word)) @@ -315,7 +350,18 @@ impl LineBuffer { } /// Cursor position *in front of* the next word to the left + /// + /// Uses Unicode UAX #29 word boundaries. For Vi-style word boundaries, + /// use [`vi_word_left_index`](Self::vi_word_left_index). + #[deprecated(note = "use emacs_word_left_index or vi_word_left_index for clarity")] pub fn word_left_index(&self) -> usize { + self.emacs_word_left_index() + } + + /// Cursor position *in front of* the next Emacs word to the left + /// + /// Uses Unicode UAX #29 word boundaries. + pub fn emacs_word_left_index(&self) -> usize { self.lines[..self.insertion_point] .split_word_bound_indices() .rfind(|(_, word)| !is_whitespace_str(word)) @@ -415,7 +461,7 @@ impl LineBuffer { /// Move cursor position *in front of* the next word to the left pub fn move_word_left(&mut self) { - self.insertion_point = self.word_left_index(); + self.insertion_point = self.emacs_word_left_index(); } /// Move cursor position *in front of* the next WORD to the left @@ -425,12 +471,12 @@ impl LineBuffer { /// Move cursor position *behind* the next word to the right pub fn move_word_right(&mut self) { - self.insertion_point = self.word_right_index(); + self.insertion_point = self.emacs_word_right_index(); } /// Move cursor position to the start of the next word pub fn move_word_right_start(&mut self) { - self.insertion_point = self.word_right_start_index(); + self.insertion_point = self.emacs_word_right_start_index(); } /// Move cursor position to the start of the next WORD @@ -440,7 +486,7 @@ impl LineBuffer { /// Move cursor position to the end of the next word pub fn move_word_right_end(&mut self) { - self.insertion_point = self.word_right_end_index(); + self.insertion_point = self.emacs_word_right_end_index(); } /// Move cursor position to the end of the next WORD @@ -561,8 +607,20 @@ impl LineBuffer { } /// Gets the range of the word the current edit position is pointing to + /// + /// Uses Unicode UAX #29 word boundaries. For Vi-style word boundaries, + /// use [`vi_current_word_range`](Self::vi_current_word_range). + #[deprecated(note = "use emacs_current_word_range or vi_current_word_range for clarity")] + #[allow(deprecated)] pub fn current_word_range(&self) -> Range { - let right_index = self.word_right_index(); + self.emacs_current_word_range() + } + + /// Gets the range of the Emacs word the current edit position is pointing to + /// + /// Uses Unicode UAX #29 word boundaries. + pub fn emacs_current_word_range(&self) -> Range { + let right_index = self.emacs_word_right_index(); let left_index = self.lines[..right_index] .split_word_bound_indices() .rfind(|(_, word)| !is_whitespace_str(word)) @@ -602,7 +660,7 @@ impl LineBuffer { /// Uppercases the current word pub fn uppercase_word(&mut self) { - let change_range = self.current_word_range(); + let change_range = self.emacs_current_word_range(); let uppercased = self.get_buffer()[change_range.clone()].to_uppercase(); self.replace_range(change_range, &uppercased); self.move_word_right(); @@ -610,7 +668,7 @@ impl LineBuffer { /// Lowercases the current word pub fn lowercase_word(&mut self) { - let change_range = self.current_word_range(); + let change_range = self.emacs_current_word_range(); let uppercased = self.get_buffer()[change_range.clone()].to_lowercase(); self.replace_range(change_range, &uppercased); self.move_word_right(); @@ -678,22 +736,22 @@ impl LineBuffer { /// Deletes one word to the left pub fn delete_word_left(&mut self) { - let left_word_index = self.word_left_index(); + let left_word_index = self.emacs_word_left_index(); self.clear_range(left_word_index..self.insertion_point()); self.insertion_point = left_word_index; } /// Deletes one word to the right pub fn delete_word_right(&mut self) { - let right_word_index = self.word_right_index(); + let right_word_index = self.emacs_word_right_index(); self.clear_range(self.insertion_point()..right_word_index); } /// Swaps current word with word on right pub fn swap_words(&mut self) { - let word_1_range = self.current_word_range(); + let word_1_range = self.emacs_current_word_range(); self.move_word_right(); - let word_2_range = self.current_word_range(); + let word_2_range = self.emacs_current_word_range(); if word_1_range != word_2_range { self.move_word_left(); @@ -1912,7 +1970,7 @@ mod test { let mut line_buffer = buffer_with(input); line_buffer.set_insertion_point(position); - let index = line_buffer.word_left_index(); + let index = line_buffer.emacs_word_left_index(); assert_eq!(index, expected); } @@ -1947,7 +2005,7 @@ mod test { let mut line_buffer = buffer_with(input); line_buffer.set_insertion_point(position); - let index = line_buffer.word_right_start_index(); + let index = line_buffer.emacs_word_right_start_index(); assert_eq!(index, expected); } @@ -1984,7 +2042,7 @@ mod test { let mut line_buffer = buffer_with(input); line_buffer.set_insertion_point(position); - let index = line_buffer.word_right_end_index(); + let index = line_buffer.emacs_word_right_end_index(); assert_eq!(index, expected); } From 5c2a4e248b20d0f66ee17e5b9d922704912919e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20D=C3=A9saulniers?= Date: Tue, 21 Apr 2026 02:33:14 -0400 Subject: [PATCH 3/8] test(vi): add unit tests for vi_word_* functions Cover basic cases (spaces, single word, empty string, edge positions) for vi_word_right_start_index, vi_word_right_end_index, vi_word_left_index, vi_word_right_index, and vi_current_word_range. --- src/core_editor/line_buffer.rs | 81 ++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/src/core_editor/line_buffer.rs b/src/core_editor/line_buffer.rs index a1733011..d2f57bf0 100644 --- a/src/core_editor/line_buffer.rs +++ b/src/core_editor/line_buffer.rs @@ -2183,6 +2183,87 @@ mod test { assert_eq!(line_buffer.vi_word_left_index(), expected); } + // ── Vi word motion tests (basic / spaces / edge cases) ───────────── + + #[rstest] + #[case("abc def ghi", 0, 4)] // basic: skip 'abc' + space + #[case("abc def ghi", 4, 8)] // from second word + #[case("abc", 0, 3)] // single word → end + #[case("", 0, 0)] // empty string + #[case(" abc", 0, 2)] // leading spaces + fn test_vi_word_right_start_index( + #[case] input: &str, + #[case] position: usize, + #[case] expected: usize, + ) { + let mut line_buffer = buffer_with(input); + line_buffer.set_insertion_point(position); + assert_eq!(line_buffer.vi_word_right_start_index(), expected); + } + + #[rstest] + #[case("abc def ghi", 0, 2)] // end of 'abc' + #[case("abc", 1, 2)] // from middle of word + #[case("abc", 2, 2)] // already at end + #[case("abc def", 2, 6)] // skip to end of 'def' + fn test_vi_word_right_end_index( + #[case] input: &str, + #[case] position: usize, + #[case] expected: usize, + ) { + let mut line_buffer = buffer_with(input); + line_buffer.set_insertion_point(position); + assert_eq!(line_buffer.vi_word_right_end_index(), expected); + } + + #[rstest] + #[case("abc def ghi", 10, 8)] // from last word → start of 'ghi' + #[case("abc def ghi", 8, 4)] // from 'ghi' → start of 'def' + #[case("abc", 3, 0)] // single word → start + #[case("abc", 0, 0)] // already at start + fn test_vi_word_left_index( + #[case] input: &str, + #[case] position: usize, + #[case] expected: usize, + ) { + let mut line_buffer = buffer_with(input); + line_buffer.set_insertion_point(position); + assert_eq!(line_buffer.vi_word_left_index(), expected); + } + + #[rstest] + #[case("abc def", 0, 3)] // first word end + #[case("abc def", 4, 7)] // second word end + #[case("abc", 0, 3)] // single word + #[case("a.b", 0, 1)] // stops at punctuation boundary + #[case("abc...def", 0, 3)] // stops at punctuation boundary + fn test_vi_word_right_index( + #[case] input: &str, + #[case] position: usize, + #[case] expected: usize, + ) { + let mut line_buffer = buffer_with(input); + line_buffer.set_insertion_point(position); + assert_eq!(line_buffer.vi_word_right_index(), expected); + } + + #[rstest] + #[case("abc def", 0, 0..3)] // cursor on 'a' → word "abc" + #[case("abc def", 4, 4..7)] // cursor on 'd' → word "def" + #[case("abc.def", 0, 0..3)] // cursor on 'a' → word "abc" (not "abc.def") + #[case("abc.def", 3, 3..4)] // cursor on '.' → word "." + #[case("abc.def", 4, 4..7)] // cursor on 'd' → word "def" + #[case("a.b.c", 2, 2..3)] // cursor on 'b' → word "b" + fn test_vi_current_word_range( + #[case] input: &str, + #[case] position: usize, + #[case] expected: Range, + ) { + let mut line_buffer = buffer_with(input); + line_buffer.set_insertion_point(position); + assert_eq!(line_buffer.vi_current_word_range(), expected); + } + #[rstest] #[case("abc def", 0, 3)] #[case("abc def ghi", 3, 7)] From 036afbe3db4eedd3487773896705b588498abdeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20D=C3=A9saulniers?= Date: Tue, 21 Apr 2026 02:43:42 -0400 Subject: [PATCH 4/8] refactor(vi): rename Cut/CopyWordRightEnd to Cut/CopyViWordRightEnd Add Vi prefix to CutWordRightEnd, CutBigWordRightEnd, CopyWordRightEnd, and CopyBigWordRightEnd for consistency with other Vi-specific EditCommand variants. --- src/core_editor/editor.rs | 20 ++++++++++---------- src/edit_mode/vi/command.rs | 32 ++++++++++++++++---------------- src/edit_mode/vi/parser.rs | 12 ++++++------ src/enums.rs | 24 ++++++++++++------------ 4 files changed, 44 insertions(+), 44 deletions(-) diff --git a/src/core_editor/editor.rs b/src/core_editor/editor.rs index 2c432664..f116f885 100644 --- a/src/core_editor/editor.rs +++ b/src/core_editor/editor.rs @@ -113,8 +113,8 @@ impl Editor { EditCommand::CutViWordLeft => self.cut_vi_word_left(), EditCommand::CutWordRight => self.cut_word_right(), EditCommand::CutBigWordRight => self.cut_big_word_right(), - EditCommand::CutWordRightEnd => self.cut_word_right_end(), - EditCommand::CutBigWordRightEnd => self.cut_big_word_right_end(), + EditCommand::CutViWordRightEnd => self.cut_vi_word_right_end(), + EditCommand::CutViBigWordRightEnd => self.cut_vi_big_word_right_end(), EditCommand::CutWordRightToNext => self.cut_word_right_to_next(), EditCommand::CutBigWordRightToNext => self.cut_big_word_right_to_next(), EditCommand::PasteCutBufferBefore => self.insert_cut_buffer_before(), @@ -159,8 +159,8 @@ impl Editor { EditCommand::CopyViWordLeft => self.copy_vi_word_left(), EditCommand::CopyWordRight => self.copy_word_right(), EditCommand::CopyBigWordRight => self.copy_big_word_right(), - EditCommand::CopyWordRightEnd => self.copy_word_right_end(), - EditCommand::CopyBigWordRightEnd => self.copy_big_word_right_end(), + EditCommand::CopyViWordRightEnd => self.copy_vi_word_right_end(), + EditCommand::CopyViBigWordRightEnd => self.copy_vi_big_word_right_end(), EditCommand::CopyWordRightToNext => self.copy_word_right_to_next(), EditCommand::CopyBigWordRightToNext => self.copy_big_word_right_to_next(), EditCommand::CopyRightUntil(c) => self.copy_right_until_char(*c, false, true), @@ -511,7 +511,7 @@ impl Editor { /// Cut from cursor to end of next word (inclusive). /// Used by Vi `de` — the `e` motion is inclusive. - fn cut_word_right_end(&mut self) { + fn cut_vi_word_right_end(&mut self) { let insertion_offset = self.line_buffer.insertion_point(); let word_end = self.line_buffer.vi_word_right_end_index(); let inclusive_end = self.line_buffer.grapheme_right_index_from_pos(word_end); @@ -520,7 +520,7 @@ impl Editor { /// Cut from cursor to end of next WORD (inclusive). /// Used by Vi `dE` — the `E` motion is inclusive. - fn cut_big_word_right_end(&mut self) { + fn cut_vi_big_word_right_end(&mut self) { let insertion_offset = self.line_buffer.insertion_point(); let word_end = self.line_buffer.big_word_right_end_index(); let inclusive_end = self.line_buffer.grapheme_right_index_from_pos(word_end); @@ -1103,7 +1103,7 @@ impl Editor { /// Copy from cursor to end of next word (inclusive). /// Used by Vi `ye` — the `e` motion is inclusive. - pub(crate) fn copy_word_right_end(&mut self) { + pub(crate) fn copy_vi_word_right_end(&mut self) { let insertion_offset = self.line_buffer.insertion_point(); let word_end = self.line_buffer.vi_word_right_end_index(); let inclusive_end = self.line_buffer.grapheme_right_index_from_pos(word_end); @@ -1112,7 +1112,7 @@ impl Editor { /// Copy from cursor to end of next WORD (inclusive). /// Used by Vi `yE` — the `E` motion is inclusive. - pub(crate) fn copy_big_word_right_end(&mut self) { + pub(crate) fn copy_vi_big_word_right_end(&mut self) { let insertion_offset = self.line_buffer.insertion_point(); let word_end = self.line_buffer.big_word_right_end_index(); let inclusive_end = self.line_buffer.grapheme_right_index_from_pos(word_end); @@ -1269,7 +1269,7 @@ mod test { #[case("a.b.c", 2, "b.", "a.c")] // de on 'a' in "abc.def": cuts "abc" (end of word 'abc') #[case("abc.def", 0, "abc", ".def")] - fn test_cut_word_right_end( + fn test_cut_vi_word_right_end( #[case] input: &str, #[case] position: usize, #[case] expected_cut: &str, @@ -1278,7 +1278,7 @@ mod test { let mut editor = editor_with(input); editor.line_buffer.set_insertion_point(position); - editor.cut_word_right_end(); + editor.cut_vi_word_right_end(); assert_eq!(editor.get_buffer(), expected_buffer); assert_eq!(editor.cut_buffer.get().0, expected_cut); diff --git a/src/edit_mode/vi/command.rs b/src/edit_mode/vi/command.rs index d77df0e7..2fbf5bdf 100644 --- a/src/edit_mode/vi/command.rs +++ b/src/edit_mode/vi/command.rs @@ -336,11 +336,11 @@ impl Command { EditCommand::CutBigWordRightToNext, )]), Motion::NextWordEnd => { - Some(vec![ReedlineOption::Edit(EditCommand::CutWordRightEnd)]) - } - Motion::NextBigWordEnd => { - Some(vec![ReedlineOption::Edit(EditCommand::CutBigWordRightEnd)]) + Some(vec![ReedlineOption::Edit(EditCommand::CutViWordRightEnd)]) } + Motion::NextBigWordEnd => Some(vec![ReedlineOption::Edit( + EditCommand::CutViBigWordRightEnd, + )]), Motion::PreviousWord => { Some(vec![ReedlineOption::Edit(EditCommand::CutViWordLeft)]) } @@ -398,17 +398,17 @@ impl Command { ReedlineOption::Edit(EditCommand::CutToLineEnd), ]), Motion::NextWord => { - Some(vec![ReedlineOption::Edit(EditCommand::CutWordRightEnd)]) - } - Motion::NextBigWord => { - Some(vec![ReedlineOption::Edit(EditCommand::CutBigWordRightEnd)]) + Some(vec![ReedlineOption::Edit(EditCommand::CutViWordRightEnd)]) } + Motion::NextBigWord => Some(vec![ReedlineOption::Edit( + EditCommand::CutViBigWordRightEnd, + )]), Motion::NextWordEnd => { - Some(vec![ReedlineOption::Edit(EditCommand::CutWordRightEnd)]) - } - Motion::NextBigWordEnd => { - Some(vec![ReedlineOption::Edit(EditCommand::CutBigWordRightEnd)]) + Some(vec![ReedlineOption::Edit(EditCommand::CutViWordRightEnd)]) } + Motion::NextBigWordEnd => Some(vec![ReedlineOption::Edit( + EditCommand::CutViBigWordRightEnd, + )]), Motion::PreviousWord => { Some(vec![ReedlineOption::Edit(EditCommand::CutViWordLeft)]) } @@ -477,11 +477,11 @@ impl Command { EditCommand::CopyBigWordRightToNext, )]), Motion::NextWordEnd => { - Some(vec![ReedlineOption::Edit(EditCommand::CopyWordRightEnd)]) - } - Motion::NextBigWordEnd => { - Some(vec![ReedlineOption::Edit(EditCommand::CopyBigWordRightEnd)]) + Some(vec![ReedlineOption::Edit(EditCommand::CopyViWordRightEnd)]) } + Motion::NextBigWordEnd => Some(vec![ReedlineOption::Edit( + EditCommand::CopyViBigWordRightEnd, + )]), Motion::PreviousWord => { Some(vec![ReedlineOption::Edit(EditCommand::CopyViWordLeft)]) } diff --git a/src/edit_mode/vi/parser.rs b/src/edit_mode/vi/parser.rs index d860c075..4fde9b41 100644 --- a/src/edit_mode/vi/parser.rs +++ b/src/edit_mode/vi/parser.rs @@ -526,14 +526,14 @@ mod tests { ReedlineEvent::Edit(vec![EditCommand::CutCurrentLine])]))] #[case(&['d', 'w'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutWordRightToNext])]))] #[case(&['d', 'W'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutBigWordRightToNext])]))] - #[case(&['d', 'e'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutWordRightEnd])]))] + #[case(&['d', 'e'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutViWordRightEnd])]))] #[case(&['d', 'b'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutViWordLeft])]))] #[case(&['d', 'B'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutBigWordLeft])]))] #[case(&['c', 'c'], ReedlineEvent::Multiple(vec![ ReedlineEvent::Edit(vec![EditCommand::MoveToLineStart { select: false }]), ReedlineEvent::Edit(vec![EditCommand::CutToLineEnd]), ReedlineEvent::Repaint]))] - #[case(&['c', 'w'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutWordRightEnd]), ReedlineEvent::Repaint]))] - #[case(&['c', 'W'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutBigWordRightEnd]), ReedlineEvent::Repaint]))] - #[case(&['c', 'e'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutWordRightEnd]), ReedlineEvent::Repaint]))] + #[case(&['c', 'w'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutViWordRightEnd]), ReedlineEvent::Repaint]))] + #[case(&['c', 'W'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutViBigWordRightEnd]), ReedlineEvent::Repaint]))] + #[case(&['c', 'e'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutViWordRightEnd]), ReedlineEvent::Repaint]))] #[case(&['c', 'b'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutViWordLeft]), ReedlineEvent::Repaint]))] #[case(&['c', 'B'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutBigWordLeft]), ReedlineEvent::Repaint]))] #[case(&['d', 'h'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::Backspace])]))] @@ -541,7 +541,7 @@ mod tests { #[case(&['2', 'd', 'd'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutCurrentLine]), ReedlineEvent::Edit(vec![EditCommand::CutCurrentLine])]))] // #[case(&['d', 'j'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutCurrentLine]), ReedlineEvent::Edit(vec![EditCommand::CutCurrentLine])]))] // #[case(&['d', 'k'], ReedlineEvent::Multiple(vec![ReedlineEvent::Up, ReedlineEvent::Edit(vec![EditCommand::CutCurrentLine]), ReedlineEvent::Edit(vec![EditCommand::CutCurrentLine])]))] - #[case(&['d', 'E'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutBigWordRightEnd])]))] + #[case(&['d', 'E'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutViBigWordRightEnd])]))] #[case(&['d', '0'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutFromLineStart])]))] #[case(&['d', '^'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutFromLineNonBlankStart])]))] #[case(&['d', '$'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutToLineEnd])]))] @@ -551,7 +551,7 @@ mod tests { #[case(&['d', 'T', 'a'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutLeftBefore('a')])]))] #[case(&['d', 'g', 'g'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutFromStartLinewise { leave_blank_line: false }])]))] #[case(&['d', 'G'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutToEndLinewise { leave_blank_line: false }])]))] - #[case(&['c', 'E'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutBigWordRightEnd]), ReedlineEvent::Repaint]))] + #[case(&['c', 'E'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutViBigWordRightEnd]), ReedlineEvent::Repaint]))] #[case(&['c', '0'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutFromLineStart]), ReedlineEvent::Repaint]))] #[case(&['c', '^'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutFromLineNonBlankStart]), ReedlineEvent::Repaint]))] #[case(&['c', '$'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutToLineEnd]), ReedlineEvent::Repaint]))] diff --git a/src/enums.rs b/src/enums.rs index b7bcdb92..07384fca 100644 --- a/src/enums.rs +++ b/src/enums.rs @@ -321,10 +321,10 @@ pub enum EditCommand { CutBigWordRight, /// Cut from the insertion point to the end of the next word (inclusive, for Vi `de`) - CutWordRightEnd, + CutViWordRightEnd, /// Cut from the insertion point to the end of the next WORD (inclusive, for Vi `dE`) - CutBigWordRightEnd, + CutViBigWordRightEnd, /// Cut the word right of the insertion point and any following space CutWordRightToNext, @@ -461,10 +461,10 @@ pub enum EditCommand { CopyBigWordRight, /// Copy from the insertion point to the end of the next word (inclusive, for Vi `ye`) - CopyWordRightEnd, + CopyViWordRightEnd, /// Copy from the insertion point to the end of the next WORD (inclusive, for Vi `yE`) - CopyBigWordRightEnd, + CopyViBigWordRightEnd, /// Copy the word right of the insertion point and any following space CopyWordRightToNext, @@ -637,8 +637,8 @@ impl Display for EditCommand { EditCommand::CutBigWordLeft => write!(f, "CutBigWordLeft"), EditCommand::CutWordRight => write!(f, "CutWordRight"), EditCommand::CutBigWordRight => write!(f, "CutBigWordRight"), - EditCommand::CutWordRightEnd => write!(f, "CutWordRightEnd"), - EditCommand::CutBigWordRightEnd => write!(f, "CutBigWordRightEnd"), + EditCommand::CutViWordRightEnd => write!(f, "CutViWordRightEnd"), + EditCommand::CutViBigWordRightEnd => write!(f, "CutViBigWordRightEnd"), EditCommand::CutWordRightToNext => write!(f, "CutWordRightToNext"), EditCommand::CutBigWordRightToNext => write!(f, "CutBigWordRightToNext"), EditCommand::CutViWordLeft => write!(f, "CutViWordLeft"), @@ -675,8 +675,8 @@ impl Display for EditCommand { EditCommand::CopyViWordLeft => write!(f, "CopyViWordLeft"), EditCommand::CopyWordRight => write!(f, "CopyWordRight"), EditCommand::CopyBigWordRight => write!(f, "CopyBigWordRight"), - EditCommand::CopyWordRightEnd => write!(f, "CopyWordRightEnd"), - EditCommand::CopyBigWordRightEnd => write!(f, "CopyBigWordRightEnd"), + EditCommand::CopyViWordRightEnd => write!(f, "CopyViWordRightEnd"), + EditCommand::CopyViBigWordRightEnd => write!(f, "CopyViBigWordRightEnd"), EditCommand::CopyWordRightToNext => write!(f, "CopyWordRightToNext"), EditCommand::CopyBigWordRightToNext => write!(f, "CopyBigWordRightToNext"), EditCommand::CopyLeft => write!(f, "CopyLeft"), @@ -766,8 +766,8 @@ impl EditCommand { | EditCommand::CutBigWordLeft | EditCommand::CutWordRight | EditCommand::CutBigWordRight - | EditCommand::CutWordRightEnd - | EditCommand::CutBigWordRightEnd + | EditCommand::CutViWordRightEnd + | EditCommand::CutViBigWordRightEnd | EditCommand::CutWordRightToNext | EditCommand::CutBigWordRightToNext | EditCommand::CutViWordLeft @@ -810,8 +810,8 @@ impl EditCommand { | EditCommand::CopyViWordLeft | EditCommand::CopyWordRight | EditCommand::CopyBigWordRight - | EditCommand::CopyWordRightEnd - | EditCommand::CopyBigWordRightEnd + | EditCommand::CopyViWordRightEnd + | EditCommand::CopyViBigWordRightEnd | EditCommand::CopyWordRightToNext | EditCommand::CopyBigWordRightToNext | EditCommand::CopyLeft From 4c89fe25d6f3583abb3a370020dce3984231222b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20D=C3=A9saulniers?= Date: Tue, 21 Apr 2026 03:01:47 -0400 Subject: [PATCH 5/8] test(vi): add tests for Vi inclusive cut/copy functions in editor Add tests for cut_vi_big_word_right_end (dE), copy_vi_word_right_end (ye), and copy_vi_big_word_right_end (yE) which contain non-trivial inclusive-end logic via grapheme_right_index_from_pos. --- src/core_editor/editor.rs | 108 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/src/core_editor/editor.rs b/src/core_editor/editor.rs index f116f885..b60ca085 100644 --- a/src/core_editor/editor.rs +++ b/src/core_editor/editor.rs @@ -1284,6 +1284,114 @@ mod test { assert_eq!(editor.cut_buffer.get().0, expected_cut); } + #[rstest] + // dE from start of "abc-def ghi": cuts "abc-def" (inclusive end of WORD) + #[case("abc-def ghi", 0, "abc-def", " ghi")] + // dE from start of "abc def": cuts "abc" (inclusive end of WORD) + #[case("abc def", 0, "abc", " def")] + // dE on '-' in "abc-def ghi": cuts "-def" (rest of WORD) + #[case("abc-def ghi", 3, "-def", "abc ghi")] + fn test_cut_vi_big_word_right_end( + #[case] input: &str, + #[case] position: usize, + #[case] expected_cut: &str, + #[case] expected_buffer: &str, + ) { + let mut editor = editor_with(input); + editor.line_buffer.set_insertion_point(position); + + editor.cut_vi_big_word_right_end(); + + assert_eq!(editor.get_buffer(), expected_buffer); + assert_eq!(editor.cut_buffer.get().0, expected_cut); + } + + #[rstest] + // ye from start of "abc def": yanks "abc" (inclusive), buffer unchanged + #[case("abc def", 0, "abc", "abc def")] + // ye on 'a' in "abc.def": yanks "abc" + #[case("abc.def", 0, "abc", "abc.def")] + // ye on '.' in "a.b.c": yanks ".b" + #[case("a.b.c", 1, ".b", "a.b.c")] + fn test_copy_vi_word_right_end( + #[case] input: &str, + #[case] position: usize, + #[case] expected_copy: &str, + #[case] expected_buffer: &str, + ) { + let mut editor = editor_with(input); + editor.line_buffer.set_insertion_point(position); + + editor.copy_vi_word_right_end(); + + assert_eq!(editor.get_buffer(), expected_buffer); + assert_eq!(editor.cut_buffer.get().0, expected_copy); + } + + #[rstest] + // yE from start of "abc-def ghi": yanks "abc-def" (inclusive), buffer unchanged + #[case("abc-def ghi", 0, "abc-def", "abc-def ghi")] + // yE from '-' in "abc-def ghi": yanks "-def" + #[case("abc-def ghi", 3, "-def", "abc-def ghi")] + fn test_copy_vi_big_word_right_end( + #[case] input: &str, + #[case] position: usize, + #[case] expected_copy: &str, + #[case] expected_buffer: &str, + ) { + let mut editor = editor_with(input); + editor.line_buffer.set_insertion_point(position); + + editor.copy_vi_big_word_right_end(); + + assert_eq!(editor.get_buffer(), expected_buffer); + assert_eq!(editor.cut_buffer.get().0, expected_copy); + } + + #[rstest] + // db from end of "abc.def": cuts "def" + #[case("abc.def", 7, "def", "abc.")] + // db from '.' in "abc.def": cuts "." + #[case("abc.def", 4, ".", "abcdef")] + // db from end of "abc def": cuts "def" + #[case("abc def", 7, "def", "abc ")] + fn test_cut_vi_word_left( + #[case] input: &str, + #[case] position: usize, + #[case] expected_cut: &str, + #[case] expected_buffer: &str, + ) { + let mut editor = editor_with(input); + editor.line_buffer.set_insertion_point(position); + + editor.cut_vi_word_left(); + + assert_eq!(editor.get_buffer(), expected_buffer); + assert_eq!(editor.cut_buffer.get().0, expected_cut); + } + + #[rstest] + // yb from end of "abc.def": yanks "def", buffer unchanged + #[case("abc.def", 7, "def", "abc.def")] + // yb from '.' in "abc.def": yanks "." + #[case("abc.def", 4, ".", "abc.def")] + // yb from end of "abc def": yanks "def" + #[case("abc def", 7, "def", "abc def")] + fn test_copy_vi_word_left( + #[case] input: &str, + #[case] position: usize, + #[case] expected_copy: &str, + #[case] expected_buffer: &str, + ) { + let mut editor = editor_with(input); + editor.line_buffer.set_insertion_point(position); + + editor.copy_vi_word_left(); + + assert_eq!(editor.get_buffer(), expected_buffer); + assert_eq!(editor.cut_buffer.get().0, expected_copy); + } + #[rstest] #[case("hello world", 0, 'l', 1, false, "lo world")] #[case("hello world", 0, 'l', 1, true, "llo world")] From 799f07046be2ea597e04bdf8301fed5ce9c07e37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20D=C3=A9saulniers?= Date: Sat, 9 May 2026 17:39:35 -0400 Subject: [PATCH 6/8] refactor(enums): parameterize word EditCommand variants Replace 28 flat word-related EditCommand variants with 3 parameterized ones: MoveWord, CutWord, CopyWord, each taking WordFlavor, WordSize, and MotionTarget fields. Add WordFlavor (Emacs/Vi), WordSize (Word/BigWord), and MotionTarget (Left/Right/RightStart/RightEnd/RightToNext) enums. BigWord commands use wildcard flavor matching since word/WORD semantics are identical across Vi and Emacs. Invalid flavor/target combinations are caught with unreachable!(). Fix Emacs cut/copy_word_right_to_next incorrectly using vi_word_right_start_index instead of emacs_word_right_start_index. --- src/core_editor/editor.rs | 214 ++++++++++++++++++++---- src/edit_mode/emacs.rs | 50 +++++- src/edit_mode/keybindings.rs | 33 +++- src/edit_mode/vi/command.rs | 142 ++++++++++------ src/edit_mode/vi/motion.rs | 31 +++- src/edit_mode/vi/parser.rs | 34 ++-- src/enums.rs | 314 +++++++++++++++-------------------- 7 files changed, 523 insertions(+), 295 deletions(-) diff --git a/src/core_editor/editor.rs b/src/core_editor/editor.rs index b60ca085..ee195db3 100644 --- a/src/core_editor/editor.rs +++ b/src/core_editor/editor.rs @@ -1,7 +1,10 @@ use super::{edit_stack::EditStack, Clipboard, ClipboardMode, LineBuffer}; #[cfg(feature = "system_clipboard")] use crate::core_editor::get_system_clipboard; -use crate::enums::{EditType, TextObject, TextObjectScope, TextObjectType, UndoBehavior}; +use crate::enums::{ + EditType, MotionTarget, TextObject, TextObjectScope, TextObjectType, UndoBehavior, WordFlavor, + WordSize, +}; use crate::prompt::{PromptEditMode, PromptViMode}; use crate::{core_editor::get_local_clipboard, EditCommand}; use std::cmp::{max, min}; @@ -68,18 +71,69 @@ impl Editor { EditCommand::MoveLineDown { select } => self.move_line_down(*select), EditCommand::MoveLeft { select } => self.move_left(*select), EditCommand::MoveRight { select } => self.move_right(*select), - EditCommand::MoveWordLeft { select } => self.move_word_left(*select), - EditCommand::MoveBigWordLeft { select } => self.move_big_word_left(*select), - EditCommand::MoveWordRight { select } => self.move_word_right(*select), - EditCommand::MoveWordRightStart { select } => self.move_word_right_start(*select), - EditCommand::MoveBigWordRightStart { select } => { - self.move_big_word_right_start(*select) - } - EditCommand::MoveWordRightEnd { select } => self.move_word_right_end(*select), - EditCommand::MoveBigWordRightEnd { select } => self.move_big_word_right_end(*select), - EditCommand::MoveViWordLeft { select } => self.move_vi_word_left(*select), - EditCommand::MoveViWordRightStart { select } => self.move_vi_word_right_start(*select), - EditCommand::MoveViWordRightEnd { select } => self.move_vi_word_right_end(*select), + EditCommand::MoveWord { + flavor: WordFlavor::Emacs, + size: WordSize::Word, + target: MotionTarget::Left, + select, + } => self.move_word_left(*select), + // BigWord semantics are identical for Vi and Emacs (non-whitespace sequences) + EditCommand::MoveWord { + size: WordSize::BigWord, + target: MotionTarget::Left, + select, + .. + } => self.move_big_word_left(*select), + EditCommand::MoveWord { + flavor: WordFlavor::Emacs, + size: WordSize::Word, + target: MotionTarget::Right, + select, + } => self.move_word_right(*select), + EditCommand::MoveWord { + flavor: WordFlavor::Emacs, + size: WordSize::Word, + target: MotionTarget::RightStart, + select, + } => self.move_word_right_start(*select), + EditCommand::MoveWord { + size: WordSize::BigWord, + target: MotionTarget::RightStart, + select, + .. + } => self.move_big_word_right_start(*select), + EditCommand::MoveWord { + flavor: WordFlavor::Emacs, + size: WordSize::Word, + target: MotionTarget::RightEnd, + select, + } => self.move_word_right_end(*select), + EditCommand::MoveWord { + size: WordSize::BigWord, + target: MotionTarget::RightEnd, + select, + .. + } => self.move_big_word_right_end(*select), + EditCommand::MoveWord { + flavor: WordFlavor::Vi, + size: WordSize::Word, + target: MotionTarget::Left, + select, + } => self.move_vi_word_left(*select), + EditCommand::MoveWord { + flavor: WordFlavor::Vi, + size: WordSize::Word, + target: MotionTarget::RightStart, + select, + } => self.move_vi_word_right_start(*select), + EditCommand::MoveWord { + flavor: WordFlavor::Vi, + size: WordSize::Word, + target: MotionTarget::RightEnd, + select, + } => self.move_vi_word_right_end(*select), + // Remaining MoveWord combinations are not yet implemented + EditCommand::MoveWord { .. } => unreachable!("invalid MoveWord combination"), EditCommand::InsertChar(c) => self.insert_char(*c), EditCommand::Complete => {} EditCommand::InsertString(str) => self.insert_str(str), @@ -108,15 +162,58 @@ impl Editor { } EditCommand::CutToLineEnd => self.cut_to_line_end(), EditCommand::KillLine => self.kill_line(), - EditCommand::CutWordLeft => self.cut_word_left(), - EditCommand::CutBigWordLeft => self.cut_big_word_left(), - EditCommand::CutViWordLeft => self.cut_vi_word_left(), - EditCommand::CutWordRight => self.cut_word_right(), - EditCommand::CutBigWordRight => self.cut_big_word_right(), - EditCommand::CutViWordRightEnd => self.cut_vi_word_right_end(), - EditCommand::CutViBigWordRightEnd => self.cut_vi_big_word_right_end(), - EditCommand::CutWordRightToNext => self.cut_word_right_to_next(), - EditCommand::CutBigWordRightToNext => self.cut_big_word_right_to_next(), + EditCommand::CutWord { + flavor: WordFlavor::Emacs, + size: WordSize::Word, + target: MotionTarget::Left, + } => self.cut_word_left(), + EditCommand::CutWord { + size: WordSize::BigWord, + target: MotionTarget::Left, + .. + } => self.cut_big_word_left(), + EditCommand::CutWord { + flavor: WordFlavor::Vi, + size: WordSize::Word, + target: MotionTarget::Left, + } => self.cut_vi_word_left(), + EditCommand::CutWord { + flavor: WordFlavor::Emacs, + size: WordSize::Word, + target: MotionTarget::Right, + } => self.cut_word_right(), + EditCommand::CutWord { + size: WordSize::BigWord, + target: MotionTarget::Right, + .. + } => self.cut_big_word_right(), + EditCommand::CutWord { + flavor: WordFlavor::Vi, + size: WordSize::Word, + target: MotionTarget::RightEnd, + } => self.cut_vi_word_right_end(), + EditCommand::CutWord { + size: WordSize::BigWord, + target: MotionTarget::RightEnd, + .. + } => self.cut_vi_big_word_right_end(), + EditCommand::CutWord { + flavor: WordFlavor::Emacs, + size: WordSize::Word, + target: MotionTarget::RightToNext, + } => self.cut_word_right_to_next(), + EditCommand::CutWord { + flavor: WordFlavor::Vi, + size: WordSize::Word, + target: MotionTarget::RightToNext, + } => self.cut_vi_word_right_to_next(), + EditCommand::CutWord { + size: WordSize::BigWord, + target: MotionTarget::RightToNext, + .. + } => self.cut_big_word_right_to_next(), + // Remaining CutWord combinations are not yet implemented + EditCommand::CutWord { .. } => unreachable!("invalid CutWord combination"), EditCommand::PasteCutBufferBefore => self.insert_cut_buffer_before(), EditCommand::PasteCutBufferAfter => self.insert_cut_buffer_after(), EditCommand::UppercaseWord => self.line_buffer.uppercase_word(), @@ -154,15 +251,58 @@ impl Editor { EditCommand::CopyToEnd => self.copy_from_end(), EditCommand::CopyToEndLinewise => self.copy_from_end_linewise(), EditCommand::CopyToLineEnd => self.copy_to_line_end(), - EditCommand::CopyWordLeft => self.copy_word_left(), - EditCommand::CopyBigWordLeft => self.copy_big_word_left(), - EditCommand::CopyViWordLeft => self.copy_vi_word_left(), - EditCommand::CopyWordRight => self.copy_word_right(), - EditCommand::CopyBigWordRight => self.copy_big_word_right(), - EditCommand::CopyViWordRightEnd => self.copy_vi_word_right_end(), - EditCommand::CopyViBigWordRightEnd => self.copy_vi_big_word_right_end(), - EditCommand::CopyWordRightToNext => self.copy_word_right_to_next(), - EditCommand::CopyBigWordRightToNext => self.copy_big_word_right_to_next(), + EditCommand::CopyWord { + flavor: WordFlavor::Emacs, + size: WordSize::Word, + target: MotionTarget::Left, + } => self.copy_word_left(), + EditCommand::CopyWord { + size: WordSize::BigWord, + target: MotionTarget::Left, + .. + } => self.copy_big_word_left(), + EditCommand::CopyWord { + flavor: WordFlavor::Vi, + size: WordSize::Word, + target: MotionTarget::Left, + } => self.copy_vi_word_left(), + EditCommand::CopyWord { + flavor: WordFlavor::Emacs, + size: WordSize::Word, + target: MotionTarget::Right, + } => self.copy_word_right(), + EditCommand::CopyWord { + size: WordSize::BigWord, + target: MotionTarget::Right, + .. + } => self.copy_big_word_right(), + EditCommand::CopyWord { + flavor: WordFlavor::Vi, + size: WordSize::Word, + target: MotionTarget::RightEnd, + } => self.copy_vi_word_right_end(), + EditCommand::CopyWord { + size: WordSize::BigWord, + target: MotionTarget::RightEnd, + .. + } => self.copy_vi_big_word_right_end(), + EditCommand::CopyWord { + flavor: WordFlavor::Emacs, + size: WordSize::Word, + target: MotionTarget::RightToNext, + } => self.copy_word_right_to_next(), + EditCommand::CopyWord { + flavor: WordFlavor::Vi, + size: WordSize::Word, + target: MotionTarget::RightToNext, + } => self.copy_vi_word_right_to_next(), + EditCommand::CopyWord { + size: WordSize::BigWord, + target: MotionTarget::RightToNext, + .. + } => self.copy_big_word_right_to_next(), + // Remaining CopyWord combinations are not yet implemented + EditCommand::CopyWord { .. } => unreachable!("invalid CopyWord combination"), EditCommand::CopyRightUntil(c) => self.copy_right_until_char(*c, false, true), EditCommand::CopyRightBefore(c) => self.copy_right_until_char(*c, true, true), EditCommand::CopyLeftUntil(c) => self.copy_left_until_char(*c, false, true), @@ -528,6 +668,12 @@ impl Editor { } fn cut_word_right_to_next(&mut self) { + let insertion_offset = self.line_buffer.insertion_point(); + let next_word_start = self.line_buffer.emacs_word_right_start_index(); + self.cut_range(insertion_offset..next_word_start); + } + + fn cut_vi_word_right_to_next(&mut self) { let insertion_offset = self.line_buffer.insertion_point(); let next_word_start = self.line_buffer.vi_word_right_start_index(); self.cut_range(insertion_offset..next_word_start); @@ -1120,6 +1266,12 @@ impl Editor { } pub(crate) fn copy_word_right_to_next(&mut self) { + let insertion_offset = self.line_buffer.insertion_point(); + let next_word_start = self.line_buffer.emacs_word_right_start_index(); + self.copy_range(insertion_offset..next_word_start); + } + + pub(crate) fn copy_vi_word_right_to_next(&mut self) { let insertion_offset = self.line_buffer.insertion_point(); let next_word_start = self.line_buffer.vi_word_right_start_index(); self.copy_range(insertion_offset..next_word_start); diff --git a/src/edit_mode/emacs.rs b/src/edit_mode/emacs.rs index 633b8d5c..9a29d0f7 100644 --- a/src/edit_mode/emacs.rs +++ b/src/edit_mode/emacs.rs @@ -6,7 +6,7 @@ use crate::{ }, EditMode, }, - enums::{EditCommand, ReedlineEvent, ReedlineRawEvent}, + enums::{EditCommand, MotionTarget, ReedlineEvent, ReedlineRawEvent, WordFlavor, WordSize}, PromptEditMode, }; use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers, MouseEvent, MouseEventKind}; @@ -51,10 +51,26 @@ pub fn default_emacs_keybindings() -> Keybindings { KC::Char('y'), edit_bind(EC::PasteCutBufferBefore), ); - kb.add_binding(KM::CONTROL, KC::Char('w'), edit_bind(EC::CutWordLeft)); + kb.add_binding( + KM::CONTROL, + KC::Char('w'), + edit_bind(EC::CutWord { + flavor: WordFlavor::Emacs, + size: WordSize::Word, + target: MotionTarget::Left, + }), + ); kb.add_binding(KM::CONTROL, KC::Char('k'), edit_bind(EC::KillLine)); kb.add_binding(KM::CONTROL, KC::Char('u'), edit_bind(EC::CutFromStart)); - kb.add_binding(KM::ALT, KC::Char('d'), edit_bind(EC::CutWordRight)); + kb.add_binding( + KM::ALT, + KC::Char('d'), + edit_bind(EC::CutWord { + flavor: WordFlavor::Emacs, + size: WordSize::Word, + target: MotionTarget::Right, + }), + ); // Edits kb.add_binding(KM::CONTROL, KC::Char('t'), edit_bind(EC::SwapGraphemes)); @@ -63,27 +79,47 @@ pub fn default_emacs_keybindings() -> Keybindings { kb.add_binding( KM::ALT, KC::Left, - edit_bind(EC::MoveWordLeft { select: false }), + edit_bind(EC::MoveWord { + flavor: WordFlavor::Emacs, + size: WordSize::Word, + target: MotionTarget::Left, + select: false, + }), ); kb.add_binding( KM::ALT, KC::Right, ReedlineEvent::UntilFound(vec![ ReedlineEvent::HistoryHintWordComplete, - edit_bind(EC::MoveWordRight { select: false }), + edit_bind(EC::MoveWord { + flavor: WordFlavor::Emacs, + size: WordSize::Word, + target: MotionTarget::Right, + select: false, + }), ]), ); kb.add_binding( KM::ALT, KC::Char('b'), - edit_bind(EC::MoveWordLeft { select: false }), + edit_bind(EC::MoveWord { + flavor: WordFlavor::Emacs, + size: WordSize::Word, + target: MotionTarget::Left, + select: false, + }), ); kb.add_binding( KM::ALT, KC::Char('f'), ReedlineEvent::UntilFound(vec![ ReedlineEvent::HistoryHintWordComplete, - edit_bind(EC::MoveWordRight { select: false }), + edit_bind(EC::MoveWord { + flavor: WordFlavor::Emacs, + size: WordSize::Word, + target: MotionTarget::Right, + select: false, + }), ]), ); // Edits diff --git a/src/edit_mode/keybindings.rs b/src/edit_mode/keybindings.rs index 5ab53c14..a571f0c8 100644 --- a/src/edit_mode/keybindings.rs +++ b/src/edit_mode/keybindings.rs @@ -1,5 +1,8 @@ use { - crate::{enums::ReedlineEvent, EditCommand}, + crate::{ + enums::{MotionTarget, ReedlineEvent, WordFlavor, WordSize}, + EditCommand, + }, crossterm::event::{KeyCode, KeyModifiers}, serde::{Deserialize, Serialize}, std::collections::HashMap, @@ -139,14 +142,24 @@ pub fn add_common_navigation_bindings(kb: &mut Keybindings) { kb.add_binding( KM::CONTROL, KC::Left, - edit_bind(EC::MoveWordLeft { select: false }), + edit_bind(EC::MoveWord { + flavor: WordFlavor::Emacs, + size: WordSize::Word, + target: MotionTarget::Left, + select: false, + }), ); kb.add_binding( KM::CONTROL, KC::Right, ReedlineEvent::UntilFound(vec![ ReedlineEvent::HistoryHintWordComplete, - edit_bind(EC::MoveWordRight { select: false }), + edit_bind(EC::MoveWord { + flavor: WordFlavor::Emacs, + size: WordSize::Word, + target: MotionTarget::Right, + select: false, + }), ]), ); // Home/End & ctrl+a/ctrl+e @@ -272,12 +285,22 @@ pub fn add_common_selection_bindings(kb: &mut Keybindings) { kb.add_binding( KM::SHIFT | KM::CONTROL, KC::Left, - edit_bind(EC::MoveWordLeft { select: true }), + edit_bind(EC::MoveWord { + flavor: WordFlavor::Emacs, + size: WordSize::Word, + target: MotionTarget::Left, + select: true, + }), ); kb.add_binding( KM::SHIFT | KM::CONTROL, KC::Right, - edit_bind(EC::MoveWordRight { select: true }), + edit_bind(EC::MoveWord { + flavor: WordFlavor::Emacs, + size: WordSize::Word, + target: MotionTarget::Right, + select: true, + }), ); kb.add_binding( KM::SHIFT, diff --git a/src/edit_mode/vi/command.rs b/src/edit_mode/vi/command.rs index 2fbf5bdf..314a1ce2 100644 --- a/src/edit_mode/vi/command.rs +++ b/src/edit_mode/vi/command.rs @@ -1,5 +1,7 @@ use super::{motion::Motion, motion::ViCharSearch, parser::ReedlineOption, ViMode}; -use crate::enums::{TextObject, TextObjectScope, TextObjectType}; +use crate::enums::{ + MotionTarget, TextObject, TextObjectScope, TextObjectType, WordFlavor, WordSize, +}; use crate::{EditCommand, ReedlineEvent, Vi}; use std::iter::Peekable; @@ -329,24 +331,36 @@ impl Command { Self::Delete => match motion { Motion::End => Some(vec![ReedlineOption::Edit(EditCommand::CutToLineEnd)]), Motion::Line => Some(vec![ReedlineOption::Edit(EditCommand::CutCurrentLine)]), - Motion::NextWord => { - Some(vec![ReedlineOption::Edit(EditCommand::CutWordRightToNext)]) - } - Motion::NextBigWord => Some(vec![ReedlineOption::Edit( - EditCommand::CutBigWordRightToNext, - )]), - Motion::NextWordEnd => { - Some(vec![ReedlineOption::Edit(EditCommand::CutViWordRightEnd)]) - } - Motion::NextBigWordEnd => Some(vec![ReedlineOption::Edit( - EditCommand::CutViBigWordRightEnd, - )]), - Motion::PreviousWord => { - Some(vec![ReedlineOption::Edit(EditCommand::CutViWordLeft)]) - } - Motion::PreviousBigWord => { - Some(vec![ReedlineOption::Edit(EditCommand::CutBigWordLeft)]) - } + Motion::NextWord => Some(vec![ReedlineOption::Edit(EditCommand::CutWord { + flavor: WordFlavor::Vi, + size: WordSize::Word, + target: MotionTarget::RightToNext, + })]), + Motion::NextBigWord => Some(vec![ReedlineOption::Edit(EditCommand::CutWord { + flavor: WordFlavor::Vi, + size: WordSize::BigWord, + target: MotionTarget::RightToNext, + })]), + Motion::NextWordEnd => Some(vec![ReedlineOption::Edit(EditCommand::CutWord { + flavor: WordFlavor::Vi, + size: WordSize::Word, + target: MotionTarget::RightEnd, + })]), + Motion::NextBigWordEnd => Some(vec![ReedlineOption::Edit(EditCommand::CutWord { + flavor: WordFlavor::Vi, + size: WordSize::BigWord, + target: MotionTarget::RightEnd, + })]), + Motion::PreviousWord => Some(vec![ReedlineOption::Edit(EditCommand::CutWord { + flavor: WordFlavor::Vi, + size: WordSize::Word, + target: MotionTarget::Left, + })]), + Motion::PreviousBigWord => Some(vec![ReedlineOption::Edit(EditCommand::CutWord { + flavor: WordFlavor::Vi, + size: WordSize::BigWord, + target: MotionTarget::Left, + })]), Motion::RightUntil(c) => { vi_state.last_char_search = Some(ViCharSearch::ToRight(*c)); Some(vec![ReedlineOption::Edit(EditCommand::CutRightUntil(*c))]) @@ -397,23 +411,41 @@ impl Command { ReedlineOption::Edit(EditCommand::MoveToLineStart { select: false }), ReedlineOption::Edit(EditCommand::CutToLineEnd), ]), - Motion::NextWord => { - Some(vec![ReedlineOption::Edit(EditCommand::CutViWordRightEnd)]) - } - Motion::NextBigWord => Some(vec![ReedlineOption::Edit( - EditCommand::CutViBigWordRightEnd, - )]), - Motion::NextWordEnd => { - Some(vec![ReedlineOption::Edit(EditCommand::CutViWordRightEnd)]) + Motion::NextWord => Some(vec![ReedlineOption::Edit(EditCommand::CutWord { + flavor: WordFlavor::Vi, + size: WordSize::Word, + target: MotionTarget::RightEnd, + })]), + Motion::NextBigWord => Some(vec![ReedlineOption::Edit(EditCommand::CutWord { + flavor: WordFlavor::Vi, + size: WordSize::BigWord, + target: MotionTarget::RightEnd, + })]), + Motion::NextWordEnd => Some(vec![ReedlineOption::Edit(EditCommand::CutWord { + flavor: WordFlavor::Vi, + size: WordSize::Word, + target: MotionTarget::RightEnd, + })]), + Motion::NextBigWordEnd => { + Some(vec![ReedlineOption::Edit(EditCommand::CutWord { + flavor: WordFlavor::Vi, + size: WordSize::BigWord, + target: MotionTarget::RightEnd, + })]) } - Motion::NextBigWordEnd => Some(vec![ReedlineOption::Edit( - EditCommand::CutViBigWordRightEnd, - )]), Motion::PreviousWord => { - Some(vec![ReedlineOption::Edit(EditCommand::CutViWordLeft)]) + Some(vec![ReedlineOption::Edit(EditCommand::CutWord { + flavor: WordFlavor::Vi, + size: WordSize::Word, + target: MotionTarget::Left, + })]) } Motion::PreviousBigWord => { - Some(vec![ReedlineOption::Edit(EditCommand::CutBigWordLeft)]) + Some(vec![ReedlineOption::Edit(EditCommand::CutWord { + flavor: WordFlavor::Vi, + size: WordSize::BigWord, + target: MotionTarget::Left, + })]) } Motion::RightUntil(c) => { vi_state.last_char_search = Some(ViCharSearch::ToRight(*c)); @@ -470,23 +502,37 @@ impl Command { Self::Yank => match motion { Motion::End => Some(vec![ReedlineOption::Edit(EditCommand::CopyToLineEnd)]), Motion::Line => Some(vec![ReedlineOption::Edit(EditCommand::CopyCurrentLine)]), - Motion::NextWord => { - Some(vec![ReedlineOption::Edit(EditCommand::CopyWordRightToNext)]) - } - Motion::NextBigWord => Some(vec![ReedlineOption::Edit( - EditCommand::CopyBigWordRightToNext, - )]), - Motion::NextWordEnd => { - Some(vec![ReedlineOption::Edit(EditCommand::CopyViWordRightEnd)]) - } - Motion::NextBigWordEnd => Some(vec![ReedlineOption::Edit( - EditCommand::CopyViBigWordRightEnd, - )]), - Motion::PreviousWord => { - Some(vec![ReedlineOption::Edit(EditCommand::CopyViWordLeft)]) - } + Motion::NextWord => Some(vec![ReedlineOption::Edit(EditCommand::CopyWord { + flavor: WordFlavor::Vi, + size: WordSize::Word, + target: MotionTarget::RightToNext, + })]), + Motion::NextBigWord => Some(vec![ReedlineOption::Edit(EditCommand::CopyWord { + flavor: WordFlavor::Vi, + size: WordSize::BigWord, + target: MotionTarget::RightToNext, + })]), + Motion::NextWordEnd => Some(vec![ReedlineOption::Edit(EditCommand::CopyWord { + flavor: WordFlavor::Vi, + size: WordSize::Word, + target: MotionTarget::RightEnd, + })]), + Motion::NextBigWordEnd => Some(vec![ReedlineOption::Edit(EditCommand::CopyWord { + flavor: WordFlavor::Vi, + size: WordSize::BigWord, + target: MotionTarget::RightEnd, + })]), + Motion::PreviousWord => Some(vec![ReedlineOption::Edit(EditCommand::CopyWord { + flavor: WordFlavor::Vi, + size: WordSize::Word, + target: MotionTarget::Left, + })]), Motion::PreviousBigWord => { - Some(vec![ReedlineOption::Edit(EditCommand::CopyBigWordLeft)]) + Some(vec![ReedlineOption::Edit(EditCommand::CopyWord { + flavor: WordFlavor::Vi, + size: WordSize::BigWord, + target: MotionTarget::Left, + })]) } Motion::RightUntil(c) => { vi_state.last_char_search = Some(ViCharSearch::ToRight(*c)); diff --git a/src/edit_mode/vi/motion.rs b/src/edit_mode/vi/motion.rs index d525cc57..459497ba 100644 --- a/src/edit_mode/vi/motion.rs +++ b/src/edit_mode/vi/motion.rs @@ -1,5 +1,6 @@ use std::iter::Peekable; +use crate::enums::{MotionTarget, WordFlavor, WordSize}; use crate::{edit_mode::vi::ViMode, EditCommand, ReedlineEvent, Vi}; use super::parser::{ParseResult, ReedlineOption}; @@ -195,24 +196,42 @@ impl Motion { ReedlineEvent::Down, ])) }], - Motion::NextWord => vec![ReedlineOption::Edit(EditCommand::MoveViWordRightStart { + Motion::NextWord => vec![ReedlineOption::Edit(EditCommand::MoveWord { + flavor: WordFlavor::Vi, + size: WordSize::Word, + target: MotionTarget::RightStart, select: select_mode, })], - Motion::NextBigWord => vec![ReedlineOption::Edit(EditCommand::MoveBigWordRightStart { + Motion::NextBigWord => vec![ReedlineOption::Edit(EditCommand::MoveWord { + flavor: WordFlavor::Vi, + size: WordSize::BigWord, + target: MotionTarget::RightStart, select: select_mode, })], - Motion::NextWordEnd => vec![ReedlineOption::Edit(EditCommand::MoveViWordRightEnd { + Motion::NextWordEnd => vec![ReedlineOption::Edit(EditCommand::MoveWord { + flavor: WordFlavor::Vi, + size: WordSize::Word, + target: MotionTarget::RightEnd, select: select_mode, })], Motion::NextBigWordEnd => { - vec![ReedlineOption::Edit(EditCommand::MoveBigWordRightEnd { + vec![ReedlineOption::Edit(EditCommand::MoveWord { + flavor: WordFlavor::Vi, + size: WordSize::BigWord, + target: MotionTarget::RightEnd, select: select_mode, })] } - Motion::PreviousWord => vec![ReedlineOption::Edit(EditCommand::MoveViWordLeft { + Motion::PreviousWord => vec![ReedlineOption::Edit(EditCommand::MoveWord { + flavor: WordFlavor::Vi, + size: WordSize::Word, + target: MotionTarget::Left, select: select_mode, })], - Motion::PreviousBigWord => vec![ReedlineOption::Edit(EditCommand::MoveBigWordLeft { + Motion::PreviousBigWord => vec![ReedlineOption::Edit(EditCommand::MoveWord { + flavor: WordFlavor::Vi, + size: WordSize::BigWord, + target: MotionTarget::Left, select: select_mode, })], Motion::Line => vec![], // Placeholder as unusable standalone motion diff --git a/src/edit_mode/vi/parser.rs b/src/edit_mode/vi/parser.rs index 4fde9b41..9e0e2cfe 100644 --- a/src/edit_mode/vi/parser.rs +++ b/src/edit_mode/vi/parser.rs @@ -203,6 +203,7 @@ where #[cfg(test)] mod tests { use super::*; + use crate::enums::{MotionTarget, WordFlavor, WordSize}; use pretty_assertions::assert_eq; use rstest::rstest; @@ -491,9 +492,10 @@ mod tests { ReedlineEvent::Up, ])]))] #[case(&['w'], - ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::MoveViWordRightStart{select:false}])]))] + ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::MoveWord { flavor: WordFlavor::Vi, size: WordSize::Word, target: MotionTarget::RightStart, select: false } + ])]))] #[case(&['W'], - ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::MoveBigWordRightStart{select:false}])]))] + ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::MoveWord { flavor: WordFlavor::Vi, size: WordSize::BigWord, target: MotionTarget::RightStart, select: false }])]))] #[case(&['2', 'l'], ReedlineEvent::Multiple(vec![ ReedlineEvent::UntilFound(vec![ ReedlineEvent::HistoryHintComplete, @@ -524,24 +526,24 @@ mod tests { ]))] #[case(&['d', 'd'], ReedlineEvent::Multiple(vec![ ReedlineEvent::Edit(vec![EditCommand::CutCurrentLine])]))] - #[case(&['d', 'w'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutWordRightToNext])]))] - #[case(&['d', 'W'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutBigWordRightToNext])]))] - #[case(&['d', 'e'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutViWordRightEnd])]))] - #[case(&['d', 'b'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutViWordLeft])]))] - #[case(&['d', 'B'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutBigWordLeft])]))] + #[case(&['d', 'w'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutWord { flavor: WordFlavor::Vi, size: WordSize::Word, target: MotionTarget::RightToNext }])]))] + #[case(&['d', 'W'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutWord { flavor: WordFlavor::Vi, size: WordSize::BigWord, target: MotionTarget::RightToNext }])]))] + #[case(&['d', 'e'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutWord { flavor: WordFlavor::Vi, size: WordSize::Word, target: MotionTarget::RightEnd }])]))] + #[case(&['d', 'b'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutWord { flavor: WordFlavor::Vi, size: WordSize::Word, target: MotionTarget::Left }])]))] + #[case(&['d', 'B'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutWord { flavor: WordFlavor::Vi, size: WordSize::BigWord, target: MotionTarget::Left }])]))] #[case(&['c', 'c'], ReedlineEvent::Multiple(vec![ ReedlineEvent::Edit(vec![EditCommand::MoveToLineStart { select: false }]), ReedlineEvent::Edit(vec![EditCommand::CutToLineEnd]), ReedlineEvent::Repaint]))] - #[case(&['c', 'w'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutViWordRightEnd]), ReedlineEvent::Repaint]))] - #[case(&['c', 'W'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutViBigWordRightEnd]), ReedlineEvent::Repaint]))] - #[case(&['c', 'e'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutViWordRightEnd]), ReedlineEvent::Repaint]))] - #[case(&['c', 'b'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutViWordLeft]), ReedlineEvent::Repaint]))] - #[case(&['c', 'B'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutBigWordLeft]), ReedlineEvent::Repaint]))] + #[case(&['c', 'w'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutWord { flavor: WordFlavor::Vi, size: WordSize::Word, target: MotionTarget::RightEnd }]), ReedlineEvent::Repaint]))] + #[case(&['c', 'W'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutWord { flavor: WordFlavor::Vi, size: WordSize::BigWord, target: MotionTarget::RightEnd }]), ReedlineEvent::Repaint]))] + #[case(&['c', 'e'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutWord { flavor: WordFlavor::Vi, size: WordSize::Word, target: MotionTarget::RightEnd }]), ReedlineEvent::Repaint]))] + #[case(&['c', 'b'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutWord { flavor: WordFlavor::Vi, size: WordSize::Word, target: MotionTarget::Left }]), ReedlineEvent::Repaint]))] + #[case(&['c', 'B'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutWord { flavor: WordFlavor::Vi, size: WordSize::BigWord, target: MotionTarget::Left }]), ReedlineEvent::Repaint]))] #[case(&['d', 'h'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::Backspace])]))] #[case(&['d', 'l'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::Delete])]))] #[case(&['2', 'd', 'd'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutCurrentLine]), ReedlineEvent::Edit(vec![EditCommand::CutCurrentLine])]))] // #[case(&['d', 'j'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutCurrentLine]), ReedlineEvent::Edit(vec![EditCommand::CutCurrentLine])]))] // #[case(&['d', 'k'], ReedlineEvent::Multiple(vec![ReedlineEvent::Up, ReedlineEvent::Edit(vec![EditCommand::CutCurrentLine]), ReedlineEvent::Edit(vec![EditCommand::CutCurrentLine])]))] - #[case(&['d', 'E'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutViBigWordRightEnd])]))] + #[case(&['d', 'E'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutWord { flavor: WordFlavor::Vi, size: WordSize::BigWord, target: MotionTarget::RightEnd }])]))] #[case(&['d', '0'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutFromLineStart])]))] #[case(&['d', '^'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutFromLineNonBlankStart])]))] #[case(&['d', '$'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutToLineEnd])]))] @@ -551,7 +553,7 @@ mod tests { #[case(&['d', 'T', 'a'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutLeftBefore('a')])]))] #[case(&['d', 'g', 'g'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutFromStartLinewise { leave_blank_line: false }])]))] #[case(&['d', 'G'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutToEndLinewise { leave_blank_line: false }])]))] - #[case(&['c', 'E'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutViBigWordRightEnd]), ReedlineEvent::Repaint]))] + #[case(&['c', 'E'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutWord { flavor: WordFlavor::Vi, size: WordSize::BigWord, target: MotionTarget::RightEnd }]), ReedlineEvent::Repaint]))] #[case(&['c', '0'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutFromLineStart]), ReedlineEvent::Repaint]))] #[case(&['c', '^'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutFromLineNonBlankStart]), ReedlineEvent::Repaint]))] #[case(&['c', '$'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutToLineEnd]), ReedlineEvent::Repaint]))] @@ -622,9 +624,9 @@ mod tests { ])]))] #[case(&['k'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::MoveLineUp { select: true }])]))] #[case(&['w'], - ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::MoveViWordRightStart{select:true}])]))] + ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::MoveWord { flavor: WordFlavor::Vi, size: WordSize::Word, target: MotionTarget::RightStart, select: true }])]))] #[case(&['W'], - ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::MoveBigWordRightStart{select:true}])]))] + ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::MoveWord { flavor: WordFlavor::Vi, size: WordSize::BigWord, target: MotionTarget::RightStart, select: true }])]))] #[case(&['2', 'l'], ReedlineEvent::Multiple(vec![ ReedlineEvent::UntilFound(vec![ ReedlineEvent::HistoryHintComplete, diff --git a/src/enums.rs b/src/enums.rs index 07384fca..b7dd60a7 100644 --- a/src/enums.rs +++ b/src/enums.rs @@ -89,6 +89,30 @@ impl Default for TextObject { } } +#[derive(Default, Clone, Serialize, Deserialize, Eq, PartialEq, Debug)] +pub enum WordFlavor { + #[default] + Emacs, + Vi, +} + +#[derive(Default, Clone, Serialize, Deserialize, Eq, PartialEq, Debug)] +pub enum WordSize { + #[default] + Word, + BigWord, +} + +#[derive(Default, Clone, Serialize, Deserialize, Eq, PartialEq, Debug)] +pub enum MotionTarget { + #[default] + Left, + Right, + RightStart, + RightEnd, + RightToNext, +} + /// Editing actions which can be mapped to key bindings. /// /// Executed by `Reedline::run_edit_commands()` @@ -152,62 +176,14 @@ pub enum EditCommand { select: bool, }, - /// Move one word to the left - MoveWordLeft { - /// Select the text between the current cursor position and destination - select: bool, - }, - - /// Move one WORD to the left - MoveBigWordLeft { - /// Select the text between the current cursor position and destination - select: bool, - }, - - /// Move one word to the right - MoveWordRight { - /// Select the text between the current cursor position and destination - select: bool, - }, - - /// Move one word to the right, stop at start of word - MoveWordRightStart { - /// Select the text between the current cursor position and destination - select: bool, - }, - - /// Move one WORD to the right, stop at start of WORD - MoveBigWordRightStart { - /// Select the text between the current cursor position and destination - select: bool, - }, - - /// Move one word to the right, stop at end of word - MoveWordRightEnd { - /// Select the text between the current cursor position and destination - select: bool, - }, - - /// Move one WORD to the right, stop at end of WORD - MoveBigWordRightEnd { - /// Select the text between the current cursor position and destination - select: bool, - }, - - /// Move one Vi word to the left (uses Vi three-class word boundaries) - MoveViWordLeft { - /// Select the text between the current cursor position and destination - select: bool, - }, - - /// Move to the start of the next Vi word (uses Vi three-class word boundaries) - MoveViWordRightStart { - /// Select the text between the current cursor position and destination - select: bool, - }, - - /// Move to the end of the next Vi word (uses Vi three-class word boundaries) - MoveViWordRightEnd { + /// Move the cursor by word, using the specified flavor and motion target + MoveWord { + /// Which flavor of word movement to use (Emacs or Vi) + flavor: WordFlavor, + /// Whether to use "word" or "big word" movement rules + size: WordSize, + /// Which direction to move in + target: MotionTarget, /// Select the text between the current cursor position and destination select: bool, }, @@ -308,32 +284,15 @@ pub enum EditCommand { /// If the cursor is already at the end of the line, remove the newline character KillLine, - /// Cut the word left of the insertion point - CutWordLeft, - - /// Cut the WORD left of the insertion point - CutBigWordLeft, - - /// Cut the word right of the insertion point - CutWordRight, - - /// Cut the word right of the insertion point - CutBigWordRight, - - /// Cut from the insertion point to the end of the next word (inclusive, for Vi `de`) - CutViWordRightEnd, - - /// Cut from the insertion point to the end of the next WORD (inclusive, for Vi `dE`) - CutViBigWordRightEnd, - - /// Cut the word right of the insertion point and any following space - CutWordRightToNext, - - /// Cut the WORD right of the insertion point and any following space - CutBigWordRightToNext, - - /// Cut the Vi word left of the insertion point (uses Vi three-class word boundaries) - CutViWordLeft, + /// Cut a word + CutWord { + /// Which flavor of word movement to use (Emacs or Vi) + flavor: WordFlavor, + /// Whether to use "word" or "big word" movement rules + size: WordSize, + /// Which direction to move in + target: MotionTarget, + }, /// Paste the cut buffer in front of the insertion point (Emacs, vi `P`) PasteCutBufferBefore, @@ -445,32 +404,15 @@ pub enum EditCommand { /// Copy the current line CopyCurrentLine, - /// Copy the word left of the insertion point - CopyWordLeft, - - /// Copy the WORD left of the insertion point - CopyBigWordLeft, - - /// Copy the Vi word left of the insertion point (uses Vi three-class word boundaries) - CopyViWordLeft, - - /// Copy the word right of the insertion point - CopyWordRight, - - /// Copy the WORD right of the insertion point - CopyBigWordRight, - - /// Copy from the insertion point to the end of the next word (inclusive, for Vi `ye`) - CopyViWordRightEnd, - - /// Copy from the insertion point to the end of the next WORD (inclusive, for Vi `yE`) - CopyViBigWordRightEnd, - - /// Copy the word right of the insertion point and any following space - CopyWordRightToNext, - - /// Copy the WORD right of the insertion point and any following space - CopyBigWordRightToNext, + /// Copy a word + CopyWord { + /// Which flavor of word movement to use (Emacs or Vi) + flavor: WordFlavor, + /// Whether to use "word" or "big word" movement rules + size: WordSize, + /// Which direction to move in + target: MotionTarget, + }, /// Copy one character to the left CopyLeft, @@ -568,34 +510,6 @@ impl Display for EditCommand { EditCommand::MoveLineDown { .. } => write!(f, "MoveLineDown Optional[select: ]"), EditCommand::MoveLeft { .. } => write!(f, "MoveLeft Optional[select: ]"), EditCommand::MoveRight { .. } => write!(f, "MoveRight Optional[select: ]"), - EditCommand::MoveWordLeft { .. } => write!(f, "MoveWordLeft Optional[select: ]"), - EditCommand::MoveBigWordLeft { .. } => { - write!(f, "MoveBigWordLeft Optional[select: ]") - } - EditCommand::MoveWordRight { .. } => { - write!(f, "MoveWordRight Optional[select: ]") - } - EditCommand::MoveWordRightEnd { .. } => { - write!(f, "MoveWordRightEnd Optional[select: ]") - } - EditCommand::MoveBigWordRightEnd { .. } => { - write!(f, "MoveBigWordRightEnd Optional[select: ]") - } - EditCommand::MoveViWordLeft { .. } => { - write!(f, "MoveViWordLeft Optional[select: ]") - } - EditCommand::MoveViWordRightStart { .. } => { - write!(f, "MoveViWordRightStart Optional[select: ]") - } - EditCommand::MoveViWordRightEnd { .. } => { - write!(f, "MoveViWordRightEnd Optional[select: ]") - } - EditCommand::MoveWordRightStart { .. } => { - write!(f, "MoveWordRightStart Optional[select: ]") - } - EditCommand::MoveBigWordRightStart { .. } => { - write!(f, "MoveBigWordRightStart Optional[select: ]") - } EditCommand::MoveToPosition { .. } => { write!(f, "MoveToPosition Value: , Optional[select: ]") } @@ -605,6 +519,33 @@ impl Display for EditCommand { EditCommand::MoveLeftBefore { .. } => { write!(f, "MoveLeftBefore Value: , Optional[select: ]") } + EditCommand::MoveWord { + flavor, + size, + target, + .. + } => { + let flavor_str = match flavor { + WordFlavor::Emacs => "Emacs", + WordFlavor::Vi => "Vi", + }; + let size_str = match size { + WordSize::Word => "Word", + WordSize::BigWord => "BigWord", + }; + let target_str = match target { + MotionTarget::Left => "Left", + MotionTarget::Right => "Right", + MotionTarget::RightStart => "RightStart", + MotionTarget::RightEnd => "RightEnd", + MotionTarget::RightToNext => "RightToNext", + }; + write!( + f, + "MoveWord Flavor: {}, Size: {}, Target: {}, Optional[select: ]", + flavor_str, size_str, target_str + ) + } EditCommand::InsertChar(_) => write!(f, "InsertChar Value: "), EditCommand::InsertString(_) => write!(f, "InsertString Value: "), EditCommand::InsertNewline => write!(f, "InsertNewline"), @@ -633,15 +574,32 @@ impl Display for EditCommand { } EditCommand::CutToLineEnd => write!(f, "CutToLineEnd"), EditCommand::KillLine => write!(f, "KillLine"), - EditCommand::CutWordLeft => write!(f, "CutWordLeft"), - EditCommand::CutBigWordLeft => write!(f, "CutBigWordLeft"), - EditCommand::CutWordRight => write!(f, "CutWordRight"), - EditCommand::CutBigWordRight => write!(f, "CutBigWordRight"), - EditCommand::CutViWordRightEnd => write!(f, "CutViWordRightEnd"), - EditCommand::CutViBigWordRightEnd => write!(f, "CutViBigWordRightEnd"), - EditCommand::CutWordRightToNext => write!(f, "CutWordRightToNext"), - EditCommand::CutBigWordRightToNext => write!(f, "CutBigWordRightToNext"), - EditCommand::CutViWordLeft => write!(f, "CutViWordLeft"), + EditCommand::CutWord { + flavor, + size, + target, + } => { + let flavor_str = match flavor { + WordFlavor::Emacs => "Emacs", + WordFlavor::Vi => "Vi", + }; + let size_str = match size { + WordSize::Word => "Word", + WordSize::BigWord => "BigWord", + }; + let target_str = match target { + MotionTarget::Left => "Left", + MotionTarget::Right => "Right", + MotionTarget::RightStart => "RightStart", + MotionTarget::RightEnd => "RightEnd", + MotionTarget::RightToNext => "RightToNext", + }; + write!( + f, + "CutWord Flavor: {}, Size: {}, Target: {}", + flavor_str, size_str, target_str + ) + } EditCommand::PasteCutBufferBefore => write!(f, "PasteCutBufferBefore"), EditCommand::PasteCutBufferAfter => write!(f, "PasteCutBufferAfter"), EditCommand::UppercaseWord => write!(f, "UppercaseWord"), @@ -670,15 +628,32 @@ impl Display for EditCommand { EditCommand::CopyToEndLinewise => write!(f, "CopyToEndLinewise"), EditCommand::CopyToLineEnd => write!(f, "CopyToLineEnd"), EditCommand::CopyCurrentLine => write!(f, "CopyCurrentLine"), - EditCommand::CopyWordLeft => write!(f, "CopyWordLeft"), - EditCommand::CopyBigWordLeft => write!(f, "CopyBigWordLeft"), - EditCommand::CopyViWordLeft => write!(f, "CopyViWordLeft"), - EditCommand::CopyWordRight => write!(f, "CopyWordRight"), - EditCommand::CopyBigWordRight => write!(f, "CopyBigWordRight"), - EditCommand::CopyViWordRightEnd => write!(f, "CopyViWordRightEnd"), - EditCommand::CopyViBigWordRightEnd => write!(f, "CopyViBigWordRightEnd"), - EditCommand::CopyWordRightToNext => write!(f, "CopyWordRightToNext"), - EditCommand::CopyBigWordRightToNext => write!(f, "CopyBigWordRightToNext"), + EditCommand::CopyWord { + flavor, + size, + target, + } => { + let flavor_str = match flavor { + WordFlavor::Emacs => "Emacs", + WordFlavor::Vi => "Vi", + }; + let size_str = match size { + WordSize::Word => "Word", + WordSize::BigWord => "BigWord", + }; + let target_str = match target { + MotionTarget::Left => "Left", + MotionTarget::Right => "Right", + MotionTarget::RightStart => "RightStart", + MotionTarget::RightEnd => "RightEnd", + MotionTarget::RightToNext => "RightToNext", + }; + write!( + f, + "CopyWord Flavor: {}, Size: {}, Target: {}", + flavor_str, size_str, target_str + ) + } EditCommand::CopyLeft => write!(f, "CopyLeft"), EditCommand::CopyRight => write!(f, "CopyRight"), EditCommand::CopyRightUntil(_) => write!(f, "CopyRightUntil Value: "), @@ -718,16 +693,7 @@ impl EditCommand { | EditCommand::MoveLineDown { select, .. } | EditCommand::MoveLeft { select, .. } | EditCommand::MoveRight { select, .. } - | EditCommand::MoveWordLeft { select, .. } - | EditCommand::MoveBigWordLeft { select, .. } - | EditCommand::MoveWordRight { select, .. } - | EditCommand::MoveWordRightStart { select, .. } - | EditCommand::MoveBigWordRightStart { select, .. } - | EditCommand::MoveWordRightEnd { select, .. } - | EditCommand::MoveBigWordRightEnd { select, .. } - | EditCommand::MoveViWordLeft { select, .. } - | EditCommand::MoveViWordRightStart { select, .. } - | EditCommand::MoveViWordRightEnd { select, .. } + | EditCommand::MoveWord { select, .. } | EditCommand::MoveRightUntil { select, .. } | EditCommand::MoveRightBefore { select, .. } | EditCommand::MoveLeftUntil { select, .. } @@ -762,15 +728,7 @@ impl EditCommand { | EditCommand::KillLine | EditCommand::CutToEnd | EditCommand::CutToEndLinewise { .. } - | EditCommand::CutWordLeft - | EditCommand::CutBigWordLeft - | EditCommand::CutWordRight - | EditCommand::CutBigWordRight - | EditCommand::CutViWordRightEnd - | EditCommand::CutViBigWordRightEnd - | EditCommand::CutWordRightToNext - | EditCommand::CutBigWordRightToNext - | EditCommand::CutViWordLeft + | EditCommand::CutWord { .. } | EditCommand::PasteCutBufferBefore | EditCommand::PasteCutBufferAfter | EditCommand::UppercaseWord @@ -805,15 +763,7 @@ impl EditCommand { | EditCommand::CopyToEndLinewise | EditCommand::CopyToLineEnd | EditCommand::CopyCurrentLine - | EditCommand::CopyWordLeft - | EditCommand::CopyBigWordLeft - | EditCommand::CopyViWordLeft - | EditCommand::CopyWordRight - | EditCommand::CopyBigWordRight - | EditCommand::CopyViWordRightEnd - | EditCommand::CopyViBigWordRightEnd - | EditCommand::CopyWordRightToNext - | EditCommand::CopyBigWordRightToNext + | EditCommand::CopyWord { .. } | EditCommand::CopyLeft | EditCommand::CopyRight | EditCommand::CopyRightUntil(_) From 8a554d2bae7b313e353871be1d90d438ba650c4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20D=C3=A9saulniers?= Date: Sun, 10 May 2026 15:43:15 -0400 Subject: [PATCH 7/8] Revert "refactor(enums): parameterize word EditCommand variants" This reverts commit 799f07046be2ea597e04bdf8301fed5ce9c07e37. --- src/core_editor/editor.rs | 214 ++++-------------------- src/edit_mode/emacs.rs | 50 +----- src/edit_mode/keybindings.rs | 33 +--- src/edit_mode/vi/command.rs | 142 ++++++---------- src/edit_mode/vi/motion.rs | 31 +--- src/edit_mode/vi/parser.rs | 34 ++-- src/enums.rs | 314 ++++++++++++++++++++--------------- 7 files changed, 295 insertions(+), 523 deletions(-) diff --git a/src/core_editor/editor.rs b/src/core_editor/editor.rs index ee195db3..b60ca085 100644 --- a/src/core_editor/editor.rs +++ b/src/core_editor/editor.rs @@ -1,10 +1,7 @@ use super::{edit_stack::EditStack, Clipboard, ClipboardMode, LineBuffer}; #[cfg(feature = "system_clipboard")] use crate::core_editor::get_system_clipboard; -use crate::enums::{ - EditType, MotionTarget, TextObject, TextObjectScope, TextObjectType, UndoBehavior, WordFlavor, - WordSize, -}; +use crate::enums::{EditType, TextObject, TextObjectScope, TextObjectType, UndoBehavior}; use crate::prompt::{PromptEditMode, PromptViMode}; use crate::{core_editor::get_local_clipboard, EditCommand}; use std::cmp::{max, min}; @@ -71,69 +68,18 @@ impl Editor { EditCommand::MoveLineDown { select } => self.move_line_down(*select), EditCommand::MoveLeft { select } => self.move_left(*select), EditCommand::MoveRight { select } => self.move_right(*select), - EditCommand::MoveWord { - flavor: WordFlavor::Emacs, - size: WordSize::Word, - target: MotionTarget::Left, - select, - } => self.move_word_left(*select), - // BigWord semantics are identical for Vi and Emacs (non-whitespace sequences) - EditCommand::MoveWord { - size: WordSize::BigWord, - target: MotionTarget::Left, - select, - .. - } => self.move_big_word_left(*select), - EditCommand::MoveWord { - flavor: WordFlavor::Emacs, - size: WordSize::Word, - target: MotionTarget::Right, - select, - } => self.move_word_right(*select), - EditCommand::MoveWord { - flavor: WordFlavor::Emacs, - size: WordSize::Word, - target: MotionTarget::RightStart, - select, - } => self.move_word_right_start(*select), - EditCommand::MoveWord { - size: WordSize::BigWord, - target: MotionTarget::RightStart, - select, - .. - } => self.move_big_word_right_start(*select), - EditCommand::MoveWord { - flavor: WordFlavor::Emacs, - size: WordSize::Word, - target: MotionTarget::RightEnd, - select, - } => self.move_word_right_end(*select), - EditCommand::MoveWord { - size: WordSize::BigWord, - target: MotionTarget::RightEnd, - select, - .. - } => self.move_big_word_right_end(*select), - EditCommand::MoveWord { - flavor: WordFlavor::Vi, - size: WordSize::Word, - target: MotionTarget::Left, - select, - } => self.move_vi_word_left(*select), - EditCommand::MoveWord { - flavor: WordFlavor::Vi, - size: WordSize::Word, - target: MotionTarget::RightStart, - select, - } => self.move_vi_word_right_start(*select), - EditCommand::MoveWord { - flavor: WordFlavor::Vi, - size: WordSize::Word, - target: MotionTarget::RightEnd, - select, - } => self.move_vi_word_right_end(*select), - // Remaining MoveWord combinations are not yet implemented - EditCommand::MoveWord { .. } => unreachable!("invalid MoveWord combination"), + EditCommand::MoveWordLeft { select } => self.move_word_left(*select), + EditCommand::MoveBigWordLeft { select } => self.move_big_word_left(*select), + EditCommand::MoveWordRight { select } => self.move_word_right(*select), + EditCommand::MoveWordRightStart { select } => self.move_word_right_start(*select), + EditCommand::MoveBigWordRightStart { select } => { + self.move_big_word_right_start(*select) + } + EditCommand::MoveWordRightEnd { select } => self.move_word_right_end(*select), + EditCommand::MoveBigWordRightEnd { select } => self.move_big_word_right_end(*select), + EditCommand::MoveViWordLeft { select } => self.move_vi_word_left(*select), + EditCommand::MoveViWordRightStart { select } => self.move_vi_word_right_start(*select), + EditCommand::MoveViWordRightEnd { select } => self.move_vi_word_right_end(*select), EditCommand::InsertChar(c) => self.insert_char(*c), EditCommand::Complete => {} EditCommand::InsertString(str) => self.insert_str(str), @@ -162,58 +108,15 @@ impl Editor { } EditCommand::CutToLineEnd => self.cut_to_line_end(), EditCommand::KillLine => self.kill_line(), - EditCommand::CutWord { - flavor: WordFlavor::Emacs, - size: WordSize::Word, - target: MotionTarget::Left, - } => self.cut_word_left(), - EditCommand::CutWord { - size: WordSize::BigWord, - target: MotionTarget::Left, - .. - } => self.cut_big_word_left(), - EditCommand::CutWord { - flavor: WordFlavor::Vi, - size: WordSize::Word, - target: MotionTarget::Left, - } => self.cut_vi_word_left(), - EditCommand::CutWord { - flavor: WordFlavor::Emacs, - size: WordSize::Word, - target: MotionTarget::Right, - } => self.cut_word_right(), - EditCommand::CutWord { - size: WordSize::BigWord, - target: MotionTarget::Right, - .. - } => self.cut_big_word_right(), - EditCommand::CutWord { - flavor: WordFlavor::Vi, - size: WordSize::Word, - target: MotionTarget::RightEnd, - } => self.cut_vi_word_right_end(), - EditCommand::CutWord { - size: WordSize::BigWord, - target: MotionTarget::RightEnd, - .. - } => self.cut_vi_big_word_right_end(), - EditCommand::CutWord { - flavor: WordFlavor::Emacs, - size: WordSize::Word, - target: MotionTarget::RightToNext, - } => self.cut_word_right_to_next(), - EditCommand::CutWord { - flavor: WordFlavor::Vi, - size: WordSize::Word, - target: MotionTarget::RightToNext, - } => self.cut_vi_word_right_to_next(), - EditCommand::CutWord { - size: WordSize::BigWord, - target: MotionTarget::RightToNext, - .. - } => self.cut_big_word_right_to_next(), - // Remaining CutWord combinations are not yet implemented - EditCommand::CutWord { .. } => unreachable!("invalid CutWord combination"), + EditCommand::CutWordLeft => self.cut_word_left(), + EditCommand::CutBigWordLeft => self.cut_big_word_left(), + EditCommand::CutViWordLeft => self.cut_vi_word_left(), + EditCommand::CutWordRight => self.cut_word_right(), + EditCommand::CutBigWordRight => self.cut_big_word_right(), + EditCommand::CutViWordRightEnd => self.cut_vi_word_right_end(), + EditCommand::CutViBigWordRightEnd => self.cut_vi_big_word_right_end(), + EditCommand::CutWordRightToNext => self.cut_word_right_to_next(), + EditCommand::CutBigWordRightToNext => self.cut_big_word_right_to_next(), EditCommand::PasteCutBufferBefore => self.insert_cut_buffer_before(), EditCommand::PasteCutBufferAfter => self.insert_cut_buffer_after(), EditCommand::UppercaseWord => self.line_buffer.uppercase_word(), @@ -251,58 +154,15 @@ impl Editor { EditCommand::CopyToEnd => self.copy_from_end(), EditCommand::CopyToEndLinewise => self.copy_from_end_linewise(), EditCommand::CopyToLineEnd => self.copy_to_line_end(), - EditCommand::CopyWord { - flavor: WordFlavor::Emacs, - size: WordSize::Word, - target: MotionTarget::Left, - } => self.copy_word_left(), - EditCommand::CopyWord { - size: WordSize::BigWord, - target: MotionTarget::Left, - .. - } => self.copy_big_word_left(), - EditCommand::CopyWord { - flavor: WordFlavor::Vi, - size: WordSize::Word, - target: MotionTarget::Left, - } => self.copy_vi_word_left(), - EditCommand::CopyWord { - flavor: WordFlavor::Emacs, - size: WordSize::Word, - target: MotionTarget::Right, - } => self.copy_word_right(), - EditCommand::CopyWord { - size: WordSize::BigWord, - target: MotionTarget::Right, - .. - } => self.copy_big_word_right(), - EditCommand::CopyWord { - flavor: WordFlavor::Vi, - size: WordSize::Word, - target: MotionTarget::RightEnd, - } => self.copy_vi_word_right_end(), - EditCommand::CopyWord { - size: WordSize::BigWord, - target: MotionTarget::RightEnd, - .. - } => self.copy_vi_big_word_right_end(), - EditCommand::CopyWord { - flavor: WordFlavor::Emacs, - size: WordSize::Word, - target: MotionTarget::RightToNext, - } => self.copy_word_right_to_next(), - EditCommand::CopyWord { - flavor: WordFlavor::Vi, - size: WordSize::Word, - target: MotionTarget::RightToNext, - } => self.copy_vi_word_right_to_next(), - EditCommand::CopyWord { - size: WordSize::BigWord, - target: MotionTarget::RightToNext, - .. - } => self.copy_big_word_right_to_next(), - // Remaining CopyWord combinations are not yet implemented - EditCommand::CopyWord { .. } => unreachable!("invalid CopyWord combination"), + EditCommand::CopyWordLeft => self.copy_word_left(), + EditCommand::CopyBigWordLeft => self.copy_big_word_left(), + EditCommand::CopyViWordLeft => self.copy_vi_word_left(), + EditCommand::CopyWordRight => self.copy_word_right(), + EditCommand::CopyBigWordRight => self.copy_big_word_right(), + EditCommand::CopyViWordRightEnd => self.copy_vi_word_right_end(), + EditCommand::CopyViBigWordRightEnd => self.copy_vi_big_word_right_end(), + EditCommand::CopyWordRightToNext => self.copy_word_right_to_next(), + EditCommand::CopyBigWordRightToNext => self.copy_big_word_right_to_next(), EditCommand::CopyRightUntil(c) => self.copy_right_until_char(*c, false, true), EditCommand::CopyRightBefore(c) => self.copy_right_until_char(*c, true, true), EditCommand::CopyLeftUntil(c) => self.copy_left_until_char(*c, false, true), @@ -668,12 +528,6 @@ impl Editor { } fn cut_word_right_to_next(&mut self) { - let insertion_offset = self.line_buffer.insertion_point(); - let next_word_start = self.line_buffer.emacs_word_right_start_index(); - self.cut_range(insertion_offset..next_word_start); - } - - fn cut_vi_word_right_to_next(&mut self) { let insertion_offset = self.line_buffer.insertion_point(); let next_word_start = self.line_buffer.vi_word_right_start_index(); self.cut_range(insertion_offset..next_word_start); @@ -1266,12 +1120,6 @@ impl Editor { } pub(crate) fn copy_word_right_to_next(&mut self) { - let insertion_offset = self.line_buffer.insertion_point(); - let next_word_start = self.line_buffer.emacs_word_right_start_index(); - self.copy_range(insertion_offset..next_word_start); - } - - pub(crate) fn copy_vi_word_right_to_next(&mut self) { let insertion_offset = self.line_buffer.insertion_point(); let next_word_start = self.line_buffer.vi_word_right_start_index(); self.copy_range(insertion_offset..next_word_start); diff --git a/src/edit_mode/emacs.rs b/src/edit_mode/emacs.rs index 9a29d0f7..633b8d5c 100644 --- a/src/edit_mode/emacs.rs +++ b/src/edit_mode/emacs.rs @@ -6,7 +6,7 @@ use crate::{ }, EditMode, }, - enums::{EditCommand, MotionTarget, ReedlineEvent, ReedlineRawEvent, WordFlavor, WordSize}, + enums::{EditCommand, ReedlineEvent, ReedlineRawEvent}, PromptEditMode, }; use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers, MouseEvent, MouseEventKind}; @@ -51,26 +51,10 @@ pub fn default_emacs_keybindings() -> Keybindings { KC::Char('y'), edit_bind(EC::PasteCutBufferBefore), ); - kb.add_binding( - KM::CONTROL, - KC::Char('w'), - edit_bind(EC::CutWord { - flavor: WordFlavor::Emacs, - size: WordSize::Word, - target: MotionTarget::Left, - }), - ); + kb.add_binding(KM::CONTROL, KC::Char('w'), edit_bind(EC::CutWordLeft)); kb.add_binding(KM::CONTROL, KC::Char('k'), edit_bind(EC::KillLine)); kb.add_binding(KM::CONTROL, KC::Char('u'), edit_bind(EC::CutFromStart)); - kb.add_binding( - KM::ALT, - KC::Char('d'), - edit_bind(EC::CutWord { - flavor: WordFlavor::Emacs, - size: WordSize::Word, - target: MotionTarget::Right, - }), - ); + kb.add_binding(KM::ALT, KC::Char('d'), edit_bind(EC::CutWordRight)); // Edits kb.add_binding(KM::CONTROL, KC::Char('t'), edit_bind(EC::SwapGraphemes)); @@ -79,47 +63,27 @@ pub fn default_emacs_keybindings() -> Keybindings { kb.add_binding( KM::ALT, KC::Left, - edit_bind(EC::MoveWord { - flavor: WordFlavor::Emacs, - size: WordSize::Word, - target: MotionTarget::Left, - select: false, - }), + edit_bind(EC::MoveWordLeft { select: false }), ); kb.add_binding( KM::ALT, KC::Right, ReedlineEvent::UntilFound(vec![ ReedlineEvent::HistoryHintWordComplete, - edit_bind(EC::MoveWord { - flavor: WordFlavor::Emacs, - size: WordSize::Word, - target: MotionTarget::Right, - select: false, - }), + edit_bind(EC::MoveWordRight { select: false }), ]), ); kb.add_binding( KM::ALT, KC::Char('b'), - edit_bind(EC::MoveWord { - flavor: WordFlavor::Emacs, - size: WordSize::Word, - target: MotionTarget::Left, - select: false, - }), + edit_bind(EC::MoveWordLeft { select: false }), ); kb.add_binding( KM::ALT, KC::Char('f'), ReedlineEvent::UntilFound(vec![ ReedlineEvent::HistoryHintWordComplete, - edit_bind(EC::MoveWord { - flavor: WordFlavor::Emacs, - size: WordSize::Word, - target: MotionTarget::Right, - select: false, - }), + edit_bind(EC::MoveWordRight { select: false }), ]), ); // Edits diff --git a/src/edit_mode/keybindings.rs b/src/edit_mode/keybindings.rs index a571f0c8..5ab53c14 100644 --- a/src/edit_mode/keybindings.rs +++ b/src/edit_mode/keybindings.rs @@ -1,8 +1,5 @@ use { - crate::{ - enums::{MotionTarget, ReedlineEvent, WordFlavor, WordSize}, - EditCommand, - }, + crate::{enums::ReedlineEvent, EditCommand}, crossterm::event::{KeyCode, KeyModifiers}, serde::{Deserialize, Serialize}, std::collections::HashMap, @@ -142,24 +139,14 @@ pub fn add_common_navigation_bindings(kb: &mut Keybindings) { kb.add_binding( KM::CONTROL, KC::Left, - edit_bind(EC::MoveWord { - flavor: WordFlavor::Emacs, - size: WordSize::Word, - target: MotionTarget::Left, - select: false, - }), + edit_bind(EC::MoveWordLeft { select: false }), ); kb.add_binding( KM::CONTROL, KC::Right, ReedlineEvent::UntilFound(vec![ ReedlineEvent::HistoryHintWordComplete, - edit_bind(EC::MoveWord { - flavor: WordFlavor::Emacs, - size: WordSize::Word, - target: MotionTarget::Right, - select: false, - }), + edit_bind(EC::MoveWordRight { select: false }), ]), ); // Home/End & ctrl+a/ctrl+e @@ -285,22 +272,12 @@ pub fn add_common_selection_bindings(kb: &mut Keybindings) { kb.add_binding( KM::SHIFT | KM::CONTROL, KC::Left, - edit_bind(EC::MoveWord { - flavor: WordFlavor::Emacs, - size: WordSize::Word, - target: MotionTarget::Left, - select: true, - }), + edit_bind(EC::MoveWordLeft { select: true }), ); kb.add_binding( KM::SHIFT | KM::CONTROL, KC::Right, - edit_bind(EC::MoveWord { - flavor: WordFlavor::Emacs, - size: WordSize::Word, - target: MotionTarget::Right, - select: true, - }), + edit_bind(EC::MoveWordRight { select: true }), ); kb.add_binding( KM::SHIFT, diff --git a/src/edit_mode/vi/command.rs b/src/edit_mode/vi/command.rs index 314a1ce2..2fbf5bdf 100644 --- a/src/edit_mode/vi/command.rs +++ b/src/edit_mode/vi/command.rs @@ -1,7 +1,5 @@ use super::{motion::Motion, motion::ViCharSearch, parser::ReedlineOption, ViMode}; -use crate::enums::{ - MotionTarget, TextObject, TextObjectScope, TextObjectType, WordFlavor, WordSize, -}; +use crate::enums::{TextObject, TextObjectScope, TextObjectType}; use crate::{EditCommand, ReedlineEvent, Vi}; use std::iter::Peekable; @@ -331,36 +329,24 @@ impl Command { Self::Delete => match motion { Motion::End => Some(vec![ReedlineOption::Edit(EditCommand::CutToLineEnd)]), Motion::Line => Some(vec![ReedlineOption::Edit(EditCommand::CutCurrentLine)]), - Motion::NextWord => Some(vec![ReedlineOption::Edit(EditCommand::CutWord { - flavor: WordFlavor::Vi, - size: WordSize::Word, - target: MotionTarget::RightToNext, - })]), - Motion::NextBigWord => Some(vec![ReedlineOption::Edit(EditCommand::CutWord { - flavor: WordFlavor::Vi, - size: WordSize::BigWord, - target: MotionTarget::RightToNext, - })]), - Motion::NextWordEnd => Some(vec![ReedlineOption::Edit(EditCommand::CutWord { - flavor: WordFlavor::Vi, - size: WordSize::Word, - target: MotionTarget::RightEnd, - })]), - Motion::NextBigWordEnd => Some(vec![ReedlineOption::Edit(EditCommand::CutWord { - flavor: WordFlavor::Vi, - size: WordSize::BigWord, - target: MotionTarget::RightEnd, - })]), - Motion::PreviousWord => Some(vec![ReedlineOption::Edit(EditCommand::CutWord { - flavor: WordFlavor::Vi, - size: WordSize::Word, - target: MotionTarget::Left, - })]), - Motion::PreviousBigWord => Some(vec![ReedlineOption::Edit(EditCommand::CutWord { - flavor: WordFlavor::Vi, - size: WordSize::BigWord, - target: MotionTarget::Left, - })]), + Motion::NextWord => { + Some(vec![ReedlineOption::Edit(EditCommand::CutWordRightToNext)]) + } + Motion::NextBigWord => Some(vec![ReedlineOption::Edit( + EditCommand::CutBigWordRightToNext, + )]), + Motion::NextWordEnd => { + Some(vec![ReedlineOption::Edit(EditCommand::CutViWordRightEnd)]) + } + Motion::NextBigWordEnd => Some(vec![ReedlineOption::Edit( + EditCommand::CutViBigWordRightEnd, + )]), + Motion::PreviousWord => { + Some(vec![ReedlineOption::Edit(EditCommand::CutViWordLeft)]) + } + Motion::PreviousBigWord => { + Some(vec![ReedlineOption::Edit(EditCommand::CutBigWordLeft)]) + } Motion::RightUntil(c) => { vi_state.last_char_search = Some(ViCharSearch::ToRight(*c)); Some(vec![ReedlineOption::Edit(EditCommand::CutRightUntil(*c))]) @@ -411,41 +397,23 @@ impl Command { ReedlineOption::Edit(EditCommand::MoveToLineStart { select: false }), ReedlineOption::Edit(EditCommand::CutToLineEnd), ]), - Motion::NextWord => Some(vec![ReedlineOption::Edit(EditCommand::CutWord { - flavor: WordFlavor::Vi, - size: WordSize::Word, - target: MotionTarget::RightEnd, - })]), - Motion::NextBigWord => Some(vec![ReedlineOption::Edit(EditCommand::CutWord { - flavor: WordFlavor::Vi, - size: WordSize::BigWord, - target: MotionTarget::RightEnd, - })]), - Motion::NextWordEnd => Some(vec![ReedlineOption::Edit(EditCommand::CutWord { - flavor: WordFlavor::Vi, - size: WordSize::Word, - target: MotionTarget::RightEnd, - })]), - Motion::NextBigWordEnd => { - Some(vec![ReedlineOption::Edit(EditCommand::CutWord { - flavor: WordFlavor::Vi, - size: WordSize::BigWord, - target: MotionTarget::RightEnd, - })]) + Motion::NextWord => { + Some(vec![ReedlineOption::Edit(EditCommand::CutViWordRightEnd)]) + } + Motion::NextBigWord => Some(vec![ReedlineOption::Edit( + EditCommand::CutViBigWordRightEnd, + )]), + Motion::NextWordEnd => { + Some(vec![ReedlineOption::Edit(EditCommand::CutViWordRightEnd)]) } + Motion::NextBigWordEnd => Some(vec![ReedlineOption::Edit( + EditCommand::CutViBigWordRightEnd, + )]), Motion::PreviousWord => { - Some(vec![ReedlineOption::Edit(EditCommand::CutWord { - flavor: WordFlavor::Vi, - size: WordSize::Word, - target: MotionTarget::Left, - })]) + Some(vec![ReedlineOption::Edit(EditCommand::CutViWordLeft)]) } Motion::PreviousBigWord => { - Some(vec![ReedlineOption::Edit(EditCommand::CutWord { - flavor: WordFlavor::Vi, - size: WordSize::BigWord, - target: MotionTarget::Left, - })]) + Some(vec![ReedlineOption::Edit(EditCommand::CutBigWordLeft)]) } Motion::RightUntil(c) => { vi_state.last_char_search = Some(ViCharSearch::ToRight(*c)); @@ -502,37 +470,23 @@ impl Command { Self::Yank => match motion { Motion::End => Some(vec![ReedlineOption::Edit(EditCommand::CopyToLineEnd)]), Motion::Line => Some(vec![ReedlineOption::Edit(EditCommand::CopyCurrentLine)]), - Motion::NextWord => Some(vec![ReedlineOption::Edit(EditCommand::CopyWord { - flavor: WordFlavor::Vi, - size: WordSize::Word, - target: MotionTarget::RightToNext, - })]), - Motion::NextBigWord => Some(vec![ReedlineOption::Edit(EditCommand::CopyWord { - flavor: WordFlavor::Vi, - size: WordSize::BigWord, - target: MotionTarget::RightToNext, - })]), - Motion::NextWordEnd => Some(vec![ReedlineOption::Edit(EditCommand::CopyWord { - flavor: WordFlavor::Vi, - size: WordSize::Word, - target: MotionTarget::RightEnd, - })]), - Motion::NextBigWordEnd => Some(vec![ReedlineOption::Edit(EditCommand::CopyWord { - flavor: WordFlavor::Vi, - size: WordSize::BigWord, - target: MotionTarget::RightEnd, - })]), - Motion::PreviousWord => Some(vec![ReedlineOption::Edit(EditCommand::CopyWord { - flavor: WordFlavor::Vi, - size: WordSize::Word, - target: MotionTarget::Left, - })]), + Motion::NextWord => { + Some(vec![ReedlineOption::Edit(EditCommand::CopyWordRightToNext)]) + } + Motion::NextBigWord => Some(vec![ReedlineOption::Edit( + EditCommand::CopyBigWordRightToNext, + )]), + Motion::NextWordEnd => { + Some(vec![ReedlineOption::Edit(EditCommand::CopyViWordRightEnd)]) + } + Motion::NextBigWordEnd => Some(vec![ReedlineOption::Edit( + EditCommand::CopyViBigWordRightEnd, + )]), + Motion::PreviousWord => { + Some(vec![ReedlineOption::Edit(EditCommand::CopyViWordLeft)]) + } Motion::PreviousBigWord => { - Some(vec![ReedlineOption::Edit(EditCommand::CopyWord { - flavor: WordFlavor::Vi, - size: WordSize::BigWord, - target: MotionTarget::Left, - })]) + Some(vec![ReedlineOption::Edit(EditCommand::CopyBigWordLeft)]) } Motion::RightUntil(c) => { vi_state.last_char_search = Some(ViCharSearch::ToRight(*c)); diff --git a/src/edit_mode/vi/motion.rs b/src/edit_mode/vi/motion.rs index 459497ba..d525cc57 100644 --- a/src/edit_mode/vi/motion.rs +++ b/src/edit_mode/vi/motion.rs @@ -1,6 +1,5 @@ use std::iter::Peekable; -use crate::enums::{MotionTarget, WordFlavor, WordSize}; use crate::{edit_mode::vi::ViMode, EditCommand, ReedlineEvent, Vi}; use super::parser::{ParseResult, ReedlineOption}; @@ -196,42 +195,24 @@ impl Motion { ReedlineEvent::Down, ])) }], - Motion::NextWord => vec![ReedlineOption::Edit(EditCommand::MoveWord { - flavor: WordFlavor::Vi, - size: WordSize::Word, - target: MotionTarget::RightStart, + Motion::NextWord => vec![ReedlineOption::Edit(EditCommand::MoveViWordRightStart { select: select_mode, })], - Motion::NextBigWord => vec![ReedlineOption::Edit(EditCommand::MoveWord { - flavor: WordFlavor::Vi, - size: WordSize::BigWord, - target: MotionTarget::RightStart, + Motion::NextBigWord => vec![ReedlineOption::Edit(EditCommand::MoveBigWordRightStart { select: select_mode, })], - Motion::NextWordEnd => vec![ReedlineOption::Edit(EditCommand::MoveWord { - flavor: WordFlavor::Vi, - size: WordSize::Word, - target: MotionTarget::RightEnd, + Motion::NextWordEnd => vec![ReedlineOption::Edit(EditCommand::MoveViWordRightEnd { select: select_mode, })], Motion::NextBigWordEnd => { - vec![ReedlineOption::Edit(EditCommand::MoveWord { - flavor: WordFlavor::Vi, - size: WordSize::BigWord, - target: MotionTarget::RightEnd, + vec![ReedlineOption::Edit(EditCommand::MoveBigWordRightEnd { select: select_mode, })] } - Motion::PreviousWord => vec![ReedlineOption::Edit(EditCommand::MoveWord { - flavor: WordFlavor::Vi, - size: WordSize::Word, - target: MotionTarget::Left, + Motion::PreviousWord => vec![ReedlineOption::Edit(EditCommand::MoveViWordLeft { select: select_mode, })], - Motion::PreviousBigWord => vec![ReedlineOption::Edit(EditCommand::MoveWord { - flavor: WordFlavor::Vi, - size: WordSize::BigWord, - target: MotionTarget::Left, + Motion::PreviousBigWord => vec![ReedlineOption::Edit(EditCommand::MoveBigWordLeft { select: select_mode, })], Motion::Line => vec![], // Placeholder as unusable standalone motion diff --git a/src/edit_mode/vi/parser.rs b/src/edit_mode/vi/parser.rs index 9e0e2cfe..4fde9b41 100644 --- a/src/edit_mode/vi/parser.rs +++ b/src/edit_mode/vi/parser.rs @@ -203,7 +203,6 @@ where #[cfg(test)] mod tests { use super::*; - use crate::enums::{MotionTarget, WordFlavor, WordSize}; use pretty_assertions::assert_eq; use rstest::rstest; @@ -492,10 +491,9 @@ mod tests { ReedlineEvent::Up, ])]))] #[case(&['w'], - ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::MoveWord { flavor: WordFlavor::Vi, size: WordSize::Word, target: MotionTarget::RightStart, select: false } - ])]))] + ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::MoveViWordRightStart{select:false}])]))] #[case(&['W'], - ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::MoveWord { flavor: WordFlavor::Vi, size: WordSize::BigWord, target: MotionTarget::RightStart, select: false }])]))] + ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::MoveBigWordRightStart{select:false}])]))] #[case(&['2', 'l'], ReedlineEvent::Multiple(vec![ ReedlineEvent::UntilFound(vec![ ReedlineEvent::HistoryHintComplete, @@ -526,24 +524,24 @@ mod tests { ]))] #[case(&['d', 'd'], ReedlineEvent::Multiple(vec![ ReedlineEvent::Edit(vec![EditCommand::CutCurrentLine])]))] - #[case(&['d', 'w'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutWord { flavor: WordFlavor::Vi, size: WordSize::Word, target: MotionTarget::RightToNext }])]))] - #[case(&['d', 'W'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutWord { flavor: WordFlavor::Vi, size: WordSize::BigWord, target: MotionTarget::RightToNext }])]))] - #[case(&['d', 'e'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutWord { flavor: WordFlavor::Vi, size: WordSize::Word, target: MotionTarget::RightEnd }])]))] - #[case(&['d', 'b'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutWord { flavor: WordFlavor::Vi, size: WordSize::Word, target: MotionTarget::Left }])]))] - #[case(&['d', 'B'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutWord { flavor: WordFlavor::Vi, size: WordSize::BigWord, target: MotionTarget::Left }])]))] + #[case(&['d', 'w'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutWordRightToNext])]))] + #[case(&['d', 'W'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutBigWordRightToNext])]))] + #[case(&['d', 'e'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutViWordRightEnd])]))] + #[case(&['d', 'b'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutViWordLeft])]))] + #[case(&['d', 'B'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutBigWordLeft])]))] #[case(&['c', 'c'], ReedlineEvent::Multiple(vec![ ReedlineEvent::Edit(vec![EditCommand::MoveToLineStart { select: false }]), ReedlineEvent::Edit(vec![EditCommand::CutToLineEnd]), ReedlineEvent::Repaint]))] - #[case(&['c', 'w'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutWord { flavor: WordFlavor::Vi, size: WordSize::Word, target: MotionTarget::RightEnd }]), ReedlineEvent::Repaint]))] - #[case(&['c', 'W'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutWord { flavor: WordFlavor::Vi, size: WordSize::BigWord, target: MotionTarget::RightEnd }]), ReedlineEvent::Repaint]))] - #[case(&['c', 'e'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutWord { flavor: WordFlavor::Vi, size: WordSize::Word, target: MotionTarget::RightEnd }]), ReedlineEvent::Repaint]))] - #[case(&['c', 'b'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutWord { flavor: WordFlavor::Vi, size: WordSize::Word, target: MotionTarget::Left }]), ReedlineEvent::Repaint]))] - #[case(&['c', 'B'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutWord { flavor: WordFlavor::Vi, size: WordSize::BigWord, target: MotionTarget::Left }]), ReedlineEvent::Repaint]))] + #[case(&['c', 'w'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutViWordRightEnd]), ReedlineEvent::Repaint]))] + #[case(&['c', 'W'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutViBigWordRightEnd]), ReedlineEvent::Repaint]))] + #[case(&['c', 'e'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutViWordRightEnd]), ReedlineEvent::Repaint]))] + #[case(&['c', 'b'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutViWordLeft]), ReedlineEvent::Repaint]))] + #[case(&['c', 'B'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutBigWordLeft]), ReedlineEvent::Repaint]))] #[case(&['d', 'h'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::Backspace])]))] #[case(&['d', 'l'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::Delete])]))] #[case(&['2', 'd', 'd'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutCurrentLine]), ReedlineEvent::Edit(vec![EditCommand::CutCurrentLine])]))] // #[case(&['d', 'j'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutCurrentLine]), ReedlineEvent::Edit(vec![EditCommand::CutCurrentLine])]))] // #[case(&['d', 'k'], ReedlineEvent::Multiple(vec![ReedlineEvent::Up, ReedlineEvent::Edit(vec![EditCommand::CutCurrentLine]), ReedlineEvent::Edit(vec![EditCommand::CutCurrentLine])]))] - #[case(&['d', 'E'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutWord { flavor: WordFlavor::Vi, size: WordSize::BigWord, target: MotionTarget::RightEnd }])]))] + #[case(&['d', 'E'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutViBigWordRightEnd])]))] #[case(&['d', '0'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutFromLineStart])]))] #[case(&['d', '^'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutFromLineNonBlankStart])]))] #[case(&['d', '$'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutToLineEnd])]))] @@ -553,7 +551,7 @@ mod tests { #[case(&['d', 'T', 'a'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutLeftBefore('a')])]))] #[case(&['d', 'g', 'g'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutFromStartLinewise { leave_blank_line: false }])]))] #[case(&['d', 'G'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutToEndLinewise { leave_blank_line: false }])]))] - #[case(&['c', 'E'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutWord { flavor: WordFlavor::Vi, size: WordSize::BigWord, target: MotionTarget::RightEnd }]), ReedlineEvent::Repaint]))] + #[case(&['c', 'E'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutViBigWordRightEnd]), ReedlineEvent::Repaint]))] #[case(&['c', '0'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutFromLineStart]), ReedlineEvent::Repaint]))] #[case(&['c', '^'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutFromLineNonBlankStart]), ReedlineEvent::Repaint]))] #[case(&['c', '$'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutToLineEnd]), ReedlineEvent::Repaint]))] @@ -624,9 +622,9 @@ mod tests { ])]))] #[case(&['k'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::MoveLineUp { select: true }])]))] #[case(&['w'], - ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::MoveWord { flavor: WordFlavor::Vi, size: WordSize::Word, target: MotionTarget::RightStart, select: true }])]))] + ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::MoveViWordRightStart{select:true}])]))] #[case(&['W'], - ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::MoveWord { flavor: WordFlavor::Vi, size: WordSize::BigWord, target: MotionTarget::RightStart, select: true }])]))] + ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::MoveBigWordRightStart{select:true}])]))] #[case(&['2', 'l'], ReedlineEvent::Multiple(vec![ ReedlineEvent::UntilFound(vec![ ReedlineEvent::HistoryHintComplete, diff --git a/src/enums.rs b/src/enums.rs index b7dd60a7..07384fca 100644 --- a/src/enums.rs +++ b/src/enums.rs @@ -89,30 +89,6 @@ impl Default for TextObject { } } -#[derive(Default, Clone, Serialize, Deserialize, Eq, PartialEq, Debug)] -pub enum WordFlavor { - #[default] - Emacs, - Vi, -} - -#[derive(Default, Clone, Serialize, Deserialize, Eq, PartialEq, Debug)] -pub enum WordSize { - #[default] - Word, - BigWord, -} - -#[derive(Default, Clone, Serialize, Deserialize, Eq, PartialEq, Debug)] -pub enum MotionTarget { - #[default] - Left, - Right, - RightStart, - RightEnd, - RightToNext, -} - /// Editing actions which can be mapped to key bindings. /// /// Executed by `Reedline::run_edit_commands()` @@ -176,14 +152,62 @@ pub enum EditCommand { select: bool, }, - /// Move the cursor by word, using the specified flavor and motion target - MoveWord { - /// Which flavor of word movement to use (Emacs or Vi) - flavor: WordFlavor, - /// Whether to use "word" or "big word" movement rules - size: WordSize, - /// Which direction to move in - target: MotionTarget, + /// Move one word to the left + MoveWordLeft { + /// Select the text between the current cursor position and destination + select: bool, + }, + + /// Move one WORD to the left + MoveBigWordLeft { + /// Select the text between the current cursor position and destination + select: bool, + }, + + /// Move one word to the right + MoveWordRight { + /// Select the text between the current cursor position and destination + select: bool, + }, + + /// Move one word to the right, stop at start of word + MoveWordRightStart { + /// Select the text between the current cursor position and destination + select: bool, + }, + + /// Move one WORD to the right, stop at start of WORD + MoveBigWordRightStart { + /// Select the text between the current cursor position and destination + select: bool, + }, + + /// Move one word to the right, stop at end of word + MoveWordRightEnd { + /// Select the text between the current cursor position and destination + select: bool, + }, + + /// Move one WORD to the right, stop at end of WORD + MoveBigWordRightEnd { + /// Select the text between the current cursor position and destination + select: bool, + }, + + /// Move one Vi word to the left (uses Vi three-class word boundaries) + MoveViWordLeft { + /// Select the text between the current cursor position and destination + select: bool, + }, + + /// Move to the start of the next Vi word (uses Vi three-class word boundaries) + MoveViWordRightStart { + /// Select the text between the current cursor position and destination + select: bool, + }, + + /// Move to the end of the next Vi word (uses Vi three-class word boundaries) + MoveViWordRightEnd { /// Select the text between the current cursor position and destination select: bool, }, @@ -284,15 +308,32 @@ pub enum EditCommand { /// If the cursor is already at the end of the line, remove the newline character KillLine, - /// Cut a word - CutWord { - /// Which flavor of word movement to use (Emacs or Vi) - flavor: WordFlavor, - /// Whether to use "word" or "big word" movement rules - size: WordSize, - /// Which direction to move in - target: MotionTarget, - }, + /// Cut the word left of the insertion point + CutWordLeft, + + /// Cut the WORD left of the insertion point + CutBigWordLeft, + + /// Cut the word right of the insertion point + CutWordRight, + + /// Cut the word right of the insertion point + CutBigWordRight, + + /// Cut from the insertion point to the end of the next word (inclusive, for Vi `de`) + CutViWordRightEnd, + + /// Cut from the insertion point to the end of the next WORD (inclusive, for Vi `dE`) + CutViBigWordRightEnd, + + /// Cut the word right of the insertion point and any following space + CutWordRightToNext, + + /// Cut the WORD right of the insertion point and any following space + CutBigWordRightToNext, + + /// Cut the Vi word left of the insertion point (uses Vi three-class word boundaries) + CutViWordLeft, /// Paste the cut buffer in front of the insertion point (Emacs, vi `P`) PasteCutBufferBefore, @@ -404,15 +445,32 @@ pub enum EditCommand { /// Copy the current line CopyCurrentLine, - /// Copy a word - CopyWord { - /// Which flavor of word movement to use (Emacs or Vi) - flavor: WordFlavor, - /// Whether to use "word" or "big word" movement rules - size: WordSize, - /// Which direction to move in - target: MotionTarget, - }, + /// Copy the word left of the insertion point + CopyWordLeft, + + /// Copy the WORD left of the insertion point + CopyBigWordLeft, + + /// Copy the Vi word left of the insertion point (uses Vi three-class word boundaries) + CopyViWordLeft, + + /// Copy the word right of the insertion point + CopyWordRight, + + /// Copy the WORD right of the insertion point + CopyBigWordRight, + + /// Copy from the insertion point to the end of the next word (inclusive, for Vi `ye`) + CopyViWordRightEnd, + + /// Copy from the insertion point to the end of the next WORD (inclusive, for Vi `yE`) + CopyViBigWordRightEnd, + + /// Copy the word right of the insertion point and any following space + CopyWordRightToNext, + + /// Copy the WORD right of the insertion point and any following space + CopyBigWordRightToNext, /// Copy one character to the left CopyLeft, @@ -510,6 +568,34 @@ impl Display for EditCommand { EditCommand::MoveLineDown { .. } => write!(f, "MoveLineDown Optional[select: ]"), EditCommand::MoveLeft { .. } => write!(f, "MoveLeft Optional[select: ]"), EditCommand::MoveRight { .. } => write!(f, "MoveRight Optional[select: ]"), + EditCommand::MoveWordLeft { .. } => write!(f, "MoveWordLeft Optional[select: ]"), + EditCommand::MoveBigWordLeft { .. } => { + write!(f, "MoveBigWordLeft Optional[select: ]") + } + EditCommand::MoveWordRight { .. } => { + write!(f, "MoveWordRight Optional[select: ]") + } + EditCommand::MoveWordRightEnd { .. } => { + write!(f, "MoveWordRightEnd Optional[select: ]") + } + EditCommand::MoveBigWordRightEnd { .. } => { + write!(f, "MoveBigWordRightEnd Optional[select: ]") + } + EditCommand::MoveViWordLeft { .. } => { + write!(f, "MoveViWordLeft Optional[select: ]") + } + EditCommand::MoveViWordRightStart { .. } => { + write!(f, "MoveViWordRightStart Optional[select: ]") + } + EditCommand::MoveViWordRightEnd { .. } => { + write!(f, "MoveViWordRightEnd Optional[select: ]") + } + EditCommand::MoveWordRightStart { .. } => { + write!(f, "MoveWordRightStart Optional[select: ]") + } + EditCommand::MoveBigWordRightStart { .. } => { + write!(f, "MoveBigWordRightStart Optional[select: ]") + } EditCommand::MoveToPosition { .. } => { write!(f, "MoveToPosition Value: , Optional[select: ]") } @@ -519,33 +605,6 @@ impl Display for EditCommand { EditCommand::MoveLeftBefore { .. } => { write!(f, "MoveLeftBefore Value: , Optional[select: ]") } - EditCommand::MoveWord { - flavor, - size, - target, - .. - } => { - let flavor_str = match flavor { - WordFlavor::Emacs => "Emacs", - WordFlavor::Vi => "Vi", - }; - let size_str = match size { - WordSize::Word => "Word", - WordSize::BigWord => "BigWord", - }; - let target_str = match target { - MotionTarget::Left => "Left", - MotionTarget::Right => "Right", - MotionTarget::RightStart => "RightStart", - MotionTarget::RightEnd => "RightEnd", - MotionTarget::RightToNext => "RightToNext", - }; - write!( - f, - "MoveWord Flavor: {}, Size: {}, Target: {}, Optional[select: ]", - flavor_str, size_str, target_str - ) - } EditCommand::InsertChar(_) => write!(f, "InsertChar Value: "), EditCommand::InsertString(_) => write!(f, "InsertString Value: "), EditCommand::InsertNewline => write!(f, "InsertNewline"), @@ -574,32 +633,15 @@ impl Display for EditCommand { } EditCommand::CutToLineEnd => write!(f, "CutToLineEnd"), EditCommand::KillLine => write!(f, "KillLine"), - EditCommand::CutWord { - flavor, - size, - target, - } => { - let flavor_str = match flavor { - WordFlavor::Emacs => "Emacs", - WordFlavor::Vi => "Vi", - }; - let size_str = match size { - WordSize::Word => "Word", - WordSize::BigWord => "BigWord", - }; - let target_str = match target { - MotionTarget::Left => "Left", - MotionTarget::Right => "Right", - MotionTarget::RightStart => "RightStart", - MotionTarget::RightEnd => "RightEnd", - MotionTarget::RightToNext => "RightToNext", - }; - write!( - f, - "CutWord Flavor: {}, Size: {}, Target: {}", - flavor_str, size_str, target_str - ) - } + EditCommand::CutWordLeft => write!(f, "CutWordLeft"), + EditCommand::CutBigWordLeft => write!(f, "CutBigWordLeft"), + EditCommand::CutWordRight => write!(f, "CutWordRight"), + EditCommand::CutBigWordRight => write!(f, "CutBigWordRight"), + EditCommand::CutViWordRightEnd => write!(f, "CutViWordRightEnd"), + EditCommand::CutViBigWordRightEnd => write!(f, "CutViBigWordRightEnd"), + EditCommand::CutWordRightToNext => write!(f, "CutWordRightToNext"), + EditCommand::CutBigWordRightToNext => write!(f, "CutBigWordRightToNext"), + EditCommand::CutViWordLeft => write!(f, "CutViWordLeft"), EditCommand::PasteCutBufferBefore => write!(f, "PasteCutBufferBefore"), EditCommand::PasteCutBufferAfter => write!(f, "PasteCutBufferAfter"), EditCommand::UppercaseWord => write!(f, "UppercaseWord"), @@ -628,32 +670,15 @@ impl Display for EditCommand { EditCommand::CopyToEndLinewise => write!(f, "CopyToEndLinewise"), EditCommand::CopyToLineEnd => write!(f, "CopyToLineEnd"), EditCommand::CopyCurrentLine => write!(f, "CopyCurrentLine"), - EditCommand::CopyWord { - flavor, - size, - target, - } => { - let flavor_str = match flavor { - WordFlavor::Emacs => "Emacs", - WordFlavor::Vi => "Vi", - }; - let size_str = match size { - WordSize::Word => "Word", - WordSize::BigWord => "BigWord", - }; - let target_str = match target { - MotionTarget::Left => "Left", - MotionTarget::Right => "Right", - MotionTarget::RightStart => "RightStart", - MotionTarget::RightEnd => "RightEnd", - MotionTarget::RightToNext => "RightToNext", - }; - write!( - f, - "CopyWord Flavor: {}, Size: {}, Target: {}", - flavor_str, size_str, target_str - ) - } + EditCommand::CopyWordLeft => write!(f, "CopyWordLeft"), + EditCommand::CopyBigWordLeft => write!(f, "CopyBigWordLeft"), + EditCommand::CopyViWordLeft => write!(f, "CopyViWordLeft"), + EditCommand::CopyWordRight => write!(f, "CopyWordRight"), + EditCommand::CopyBigWordRight => write!(f, "CopyBigWordRight"), + EditCommand::CopyViWordRightEnd => write!(f, "CopyViWordRightEnd"), + EditCommand::CopyViBigWordRightEnd => write!(f, "CopyViBigWordRightEnd"), + EditCommand::CopyWordRightToNext => write!(f, "CopyWordRightToNext"), + EditCommand::CopyBigWordRightToNext => write!(f, "CopyBigWordRightToNext"), EditCommand::CopyLeft => write!(f, "CopyLeft"), EditCommand::CopyRight => write!(f, "CopyRight"), EditCommand::CopyRightUntil(_) => write!(f, "CopyRightUntil Value: "), @@ -693,7 +718,16 @@ impl EditCommand { | EditCommand::MoveLineDown { select, .. } | EditCommand::MoveLeft { select, .. } | EditCommand::MoveRight { select, .. } - | EditCommand::MoveWord { select, .. } + | EditCommand::MoveWordLeft { select, .. } + | EditCommand::MoveBigWordLeft { select, .. } + | EditCommand::MoveWordRight { select, .. } + | EditCommand::MoveWordRightStart { select, .. } + | EditCommand::MoveBigWordRightStart { select, .. } + | EditCommand::MoveWordRightEnd { select, .. } + | EditCommand::MoveBigWordRightEnd { select, .. } + | EditCommand::MoveViWordLeft { select, .. } + | EditCommand::MoveViWordRightStart { select, .. } + | EditCommand::MoveViWordRightEnd { select, .. } | EditCommand::MoveRightUntil { select, .. } | EditCommand::MoveRightBefore { select, .. } | EditCommand::MoveLeftUntil { select, .. } @@ -728,7 +762,15 @@ impl EditCommand { | EditCommand::KillLine | EditCommand::CutToEnd | EditCommand::CutToEndLinewise { .. } - | EditCommand::CutWord { .. } + | EditCommand::CutWordLeft + | EditCommand::CutBigWordLeft + | EditCommand::CutWordRight + | EditCommand::CutBigWordRight + | EditCommand::CutViWordRightEnd + | EditCommand::CutViBigWordRightEnd + | EditCommand::CutWordRightToNext + | EditCommand::CutBigWordRightToNext + | EditCommand::CutViWordLeft | EditCommand::PasteCutBufferBefore | EditCommand::PasteCutBufferAfter | EditCommand::UppercaseWord @@ -763,7 +805,15 @@ impl EditCommand { | EditCommand::CopyToEndLinewise | EditCommand::CopyToLineEnd | EditCommand::CopyCurrentLine - | EditCommand::CopyWord { .. } + | EditCommand::CopyWordLeft + | EditCommand::CopyBigWordLeft + | EditCommand::CopyViWordLeft + | EditCommand::CopyWordRight + | EditCommand::CopyBigWordRight + | EditCommand::CopyViWordRightEnd + | EditCommand::CopyViBigWordRightEnd + | EditCommand::CopyWordRightToNext + | EditCommand::CopyBigWordRightToNext | EditCommand::CopyLeft | EditCommand::CopyRight | EditCommand::CopyRightUntil(_) From 85c4342f32b90a8a15bf7db1101bb989f7421e04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20D=C3=A9saulniers?= Date: Sun, 10 May 2026 16:18:19 -0400 Subject: [PATCH 8/8] refactor(enums): prefix Emacs-specific word EditCommand variants Rename word-related EditCommand variants to make the mode explicit: MoveWordLeft -> MoveEmacsWordLeft, CutWordRight -> CutEmacsWordRight, CopyWordRightToNext -> CopyEmacsWordRightToNext, etc. BigWord variants remain unprefixed since word/WORD semantics are identical across Vi and Emacs modes. Update all dispatch, keybinding, and call sites accordingly. --- src/core_editor/editor.rs | 20 +++++------ src/edit_mode/emacs.rs | 12 +++---- src/edit_mode/keybindings.rs | 8 ++--- src/edit_mode/vi/command.rs | 12 +++---- src/edit_mode/vi/parser.rs | 2 +- src/enums.rs | 68 +++++++++++++++++++----------------- 6 files changed, 62 insertions(+), 60 deletions(-) diff --git a/src/core_editor/editor.rs b/src/core_editor/editor.rs index b60ca085..6ee0d0a3 100644 --- a/src/core_editor/editor.rs +++ b/src/core_editor/editor.rs @@ -68,14 +68,14 @@ impl Editor { EditCommand::MoveLineDown { select } => self.move_line_down(*select), EditCommand::MoveLeft { select } => self.move_left(*select), EditCommand::MoveRight { select } => self.move_right(*select), - EditCommand::MoveWordLeft { select } => self.move_word_left(*select), + EditCommand::MoveEmacsWordLeft { select } => self.move_word_left(*select), EditCommand::MoveBigWordLeft { select } => self.move_big_word_left(*select), - EditCommand::MoveWordRight { select } => self.move_word_right(*select), - EditCommand::MoveWordRightStart { select } => self.move_word_right_start(*select), + EditCommand::MoveEmacsWordRight { select } => self.move_word_right(*select), + EditCommand::MoveEmacsWordRightStart { select } => self.move_word_right_start(*select), EditCommand::MoveBigWordRightStart { select } => { self.move_big_word_right_start(*select) } - EditCommand::MoveWordRightEnd { select } => self.move_word_right_end(*select), + EditCommand::MoveEmacsWordRightEnd { select } => self.move_word_right_end(*select), EditCommand::MoveBigWordRightEnd { select } => self.move_big_word_right_end(*select), EditCommand::MoveViWordLeft { select } => self.move_vi_word_left(*select), EditCommand::MoveViWordRightStart { select } => self.move_vi_word_right_start(*select), @@ -108,14 +108,14 @@ impl Editor { } EditCommand::CutToLineEnd => self.cut_to_line_end(), EditCommand::KillLine => self.kill_line(), - EditCommand::CutWordLeft => self.cut_word_left(), + EditCommand::CutEmacsWordLeft => self.cut_word_left(), EditCommand::CutBigWordLeft => self.cut_big_word_left(), EditCommand::CutViWordLeft => self.cut_vi_word_left(), - EditCommand::CutWordRight => self.cut_word_right(), + EditCommand::CutEmacsWordRight => self.cut_word_right(), EditCommand::CutBigWordRight => self.cut_big_word_right(), EditCommand::CutViWordRightEnd => self.cut_vi_word_right_end(), EditCommand::CutViBigWordRightEnd => self.cut_vi_big_word_right_end(), - EditCommand::CutWordRightToNext => self.cut_word_right_to_next(), + EditCommand::CutEmacsWordRightToNext => self.cut_word_right_to_next(), EditCommand::CutBigWordRightToNext => self.cut_big_word_right_to_next(), EditCommand::PasteCutBufferBefore => self.insert_cut_buffer_before(), EditCommand::PasteCutBufferAfter => self.insert_cut_buffer_after(), @@ -154,14 +154,14 @@ impl Editor { EditCommand::CopyToEnd => self.copy_from_end(), EditCommand::CopyToEndLinewise => self.copy_from_end_linewise(), EditCommand::CopyToLineEnd => self.copy_to_line_end(), - EditCommand::CopyWordLeft => self.copy_word_left(), + EditCommand::CopyEmacsWordLeft => self.copy_word_left(), EditCommand::CopyBigWordLeft => self.copy_big_word_left(), EditCommand::CopyViWordLeft => self.copy_vi_word_left(), - EditCommand::CopyWordRight => self.copy_word_right(), + EditCommand::CopyEmacsWordRight => self.copy_word_right(), EditCommand::CopyBigWordRight => self.copy_big_word_right(), EditCommand::CopyViWordRightEnd => self.copy_vi_word_right_end(), EditCommand::CopyViBigWordRightEnd => self.copy_vi_big_word_right_end(), - EditCommand::CopyWordRightToNext => self.copy_word_right_to_next(), + EditCommand::CopyEmacsWordRightToNext => self.copy_word_right_to_next(), EditCommand::CopyBigWordRightToNext => self.copy_big_word_right_to_next(), EditCommand::CopyRightUntil(c) => self.copy_right_until_char(*c, false, true), EditCommand::CopyRightBefore(c) => self.copy_right_until_char(*c, true, true), diff --git a/src/edit_mode/emacs.rs b/src/edit_mode/emacs.rs index 633b8d5c..d4e28e38 100644 --- a/src/edit_mode/emacs.rs +++ b/src/edit_mode/emacs.rs @@ -51,10 +51,10 @@ pub fn default_emacs_keybindings() -> Keybindings { KC::Char('y'), edit_bind(EC::PasteCutBufferBefore), ); - kb.add_binding(KM::CONTROL, KC::Char('w'), edit_bind(EC::CutWordLeft)); + kb.add_binding(KM::CONTROL, KC::Char('w'), edit_bind(EC::CutEmacsWordLeft)); kb.add_binding(KM::CONTROL, KC::Char('k'), edit_bind(EC::KillLine)); kb.add_binding(KM::CONTROL, KC::Char('u'), edit_bind(EC::CutFromStart)); - kb.add_binding(KM::ALT, KC::Char('d'), edit_bind(EC::CutWordRight)); + kb.add_binding(KM::ALT, KC::Char('d'), edit_bind(EC::CutEmacsWordRight)); // Edits kb.add_binding(KM::CONTROL, KC::Char('t'), edit_bind(EC::SwapGraphemes)); @@ -63,27 +63,27 @@ pub fn default_emacs_keybindings() -> Keybindings { kb.add_binding( KM::ALT, KC::Left, - edit_bind(EC::MoveWordLeft { select: false }), + edit_bind(EC::MoveEmacsWordLeft { select: false }), ); kb.add_binding( KM::ALT, KC::Right, ReedlineEvent::UntilFound(vec![ ReedlineEvent::HistoryHintWordComplete, - edit_bind(EC::MoveWordRight { select: false }), + edit_bind(EC::MoveEmacsWordRight { select: false }), ]), ); kb.add_binding( KM::ALT, KC::Char('b'), - edit_bind(EC::MoveWordLeft { select: false }), + edit_bind(EC::MoveEmacsWordLeft { select: false }), ); kb.add_binding( KM::ALT, KC::Char('f'), ReedlineEvent::UntilFound(vec![ ReedlineEvent::HistoryHintWordComplete, - edit_bind(EC::MoveWordRight { select: false }), + edit_bind(EC::MoveEmacsWordRight { select: false }), ]), ); // Edits diff --git a/src/edit_mode/keybindings.rs b/src/edit_mode/keybindings.rs index 5ab53c14..8f662698 100644 --- a/src/edit_mode/keybindings.rs +++ b/src/edit_mode/keybindings.rs @@ -139,14 +139,14 @@ pub fn add_common_navigation_bindings(kb: &mut Keybindings) { kb.add_binding( KM::CONTROL, KC::Left, - edit_bind(EC::MoveWordLeft { select: false }), + edit_bind(EC::MoveEmacsWordLeft { select: false }), ); kb.add_binding( KM::CONTROL, KC::Right, ReedlineEvent::UntilFound(vec![ ReedlineEvent::HistoryHintWordComplete, - edit_bind(EC::MoveWordRight { select: false }), + edit_bind(EC::MoveEmacsWordRight { select: false }), ]), ); // Home/End & ctrl+a/ctrl+e @@ -272,12 +272,12 @@ pub fn add_common_selection_bindings(kb: &mut Keybindings) { kb.add_binding( KM::SHIFT | KM::CONTROL, KC::Left, - edit_bind(EC::MoveWordLeft { select: true }), + edit_bind(EC::MoveEmacsWordLeft { select: true }), ); kb.add_binding( KM::SHIFT | KM::CONTROL, KC::Right, - edit_bind(EC::MoveWordRight { select: true }), + edit_bind(EC::MoveEmacsWordRight { select: true }), ); kb.add_binding( KM::SHIFT, diff --git a/src/edit_mode/vi/command.rs b/src/edit_mode/vi/command.rs index 2fbf5bdf..e583059e 100644 --- a/src/edit_mode/vi/command.rs +++ b/src/edit_mode/vi/command.rs @@ -329,9 +329,9 @@ impl Command { Self::Delete => match motion { Motion::End => Some(vec![ReedlineOption::Edit(EditCommand::CutToLineEnd)]), Motion::Line => Some(vec![ReedlineOption::Edit(EditCommand::CutCurrentLine)]), - Motion::NextWord => { - Some(vec![ReedlineOption::Edit(EditCommand::CutWordRightToNext)]) - } + Motion::NextWord => Some(vec![ReedlineOption::Edit( + EditCommand::CutEmacsWordRightToNext, + )]), Motion::NextBigWord => Some(vec![ReedlineOption::Edit( EditCommand::CutBigWordRightToNext, )]), @@ -470,9 +470,9 @@ impl Command { Self::Yank => match motion { Motion::End => Some(vec![ReedlineOption::Edit(EditCommand::CopyToLineEnd)]), Motion::Line => Some(vec![ReedlineOption::Edit(EditCommand::CopyCurrentLine)]), - Motion::NextWord => { - Some(vec![ReedlineOption::Edit(EditCommand::CopyWordRightToNext)]) - } + Motion::NextWord => Some(vec![ReedlineOption::Edit( + EditCommand::CopyEmacsWordRightToNext, + )]), Motion::NextBigWord => Some(vec![ReedlineOption::Edit( EditCommand::CopyBigWordRightToNext, )]), diff --git a/src/edit_mode/vi/parser.rs b/src/edit_mode/vi/parser.rs index 4fde9b41..1f13bb5a 100644 --- a/src/edit_mode/vi/parser.rs +++ b/src/edit_mode/vi/parser.rs @@ -524,7 +524,7 @@ mod tests { ]))] #[case(&['d', 'd'], ReedlineEvent::Multiple(vec![ ReedlineEvent::Edit(vec![EditCommand::CutCurrentLine])]))] - #[case(&['d', 'w'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutWordRightToNext])]))] + #[case(&['d', 'w'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutEmacsWordRightToNext])]))] #[case(&['d', 'W'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutBigWordRightToNext])]))] #[case(&['d', 'e'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutViWordRightEnd])]))] #[case(&['d', 'b'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutViWordLeft])]))] diff --git a/src/enums.rs b/src/enums.rs index 07384fca..94c094b0 100644 --- a/src/enums.rs +++ b/src/enums.rs @@ -153,7 +153,7 @@ pub enum EditCommand { }, /// Move one word to the left - MoveWordLeft { + MoveEmacsWordLeft { /// Select the text between the current cursor position and destination select: bool, }, @@ -165,13 +165,13 @@ pub enum EditCommand { }, /// Move one word to the right - MoveWordRight { + MoveEmacsWordRight { /// Select the text between the current cursor position and destination select: bool, }, /// Move one word to the right, stop at start of word - MoveWordRightStart { + MoveEmacsWordRightStart { /// Select the text between the current cursor position and destination select: bool, }, @@ -183,7 +183,7 @@ pub enum EditCommand { }, /// Move one word to the right, stop at end of word - MoveWordRightEnd { + MoveEmacsWordRightEnd { /// Select the text between the current cursor position and destination select: bool, }, @@ -309,13 +309,13 @@ pub enum EditCommand { KillLine, /// Cut the word left of the insertion point - CutWordLeft, + CutEmacsWordLeft, /// Cut the WORD left of the insertion point CutBigWordLeft, /// Cut the word right of the insertion point - CutWordRight, + CutEmacsWordRight, /// Cut the word right of the insertion point CutBigWordRight, @@ -327,7 +327,7 @@ pub enum EditCommand { CutViBigWordRightEnd, /// Cut the word right of the insertion point and any following space - CutWordRightToNext, + CutEmacsWordRightToNext, /// Cut the WORD right of the insertion point and any following space CutBigWordRightToNext, @@ -446,7 +446,7 @@ pub enum EditCommand { CopyCurrentLine, /// Copy the word left of the insertion point - CopyWordLeft, + CopyEmacsWordLeft, /// Copy the WORD left of the insertion point CopyBigWordLeft, @@ -455,7 +455,7 @@ pub enum EditCommand { CopyViWordLeft, /// Copy the word right of the insertion point - CopyWordRight, + CopyEmacsWordRight, /// Copy the WORD right of the insertion point CopyBigWordRight, @@ -467,7 +467,7 @@ pub enum EditCommand { CopyViBigWordRightEnd, /// Copy the word right of the insertion point and any following space - CopyWordRightToNext, + CopyEmacsWordRightToNext, /// Copy the WORD right of the insertion point and any following space CopyBigWordRightToNext, @@ -568,15 +568,17 @@ impl Display for EditCommand { EditCommand::MoveLineDown { .. } => write!(f, "MoveLineDown Optional[select: ]"), EditCommand::MoveLeft { .. } => write!(f, "MoveLeft Optional[select: ]"), EditCommand::MoveRight { .. } => write!(f, "MoveRight Optional[select: ]"), - EditCommand::MoveWordLeft { .. } => write!(f, "MoveWordLeft Optional[select: ]"), + EditCommand::MoveEmacsWordLeft { .. } => { + write!(f, "MoveEmacsWordLeft Optional[select: ]") + } EditCommand::MoveBigWordLeft { .. } => { write!(f, "MoveBigWordLeft Optional[select: ]") } - EditCommand::MoveWordRight { .. } => { - write!(f, "MoveWordRight Optional[select: ]") + EditCommand::MoveEmacsWordRight { .. } => { + write!(f, "MoveEmacsWordRight Optional[select: ]") } - EditCommand::MoveWordRightEnd { .. } => { - write!(f, "MoveWordRightEnd Optional[select: ]") + EditCommand::MoveEmacsWordRightEnd { .. } => { + write!(f, "MoveEmacsWordRightEnd Optional[select: ]") } EditCommand::MoveBigWordRightEnd { .. } => { write!(f, "MoveBigWordRightEnd Optional[select: ]") @@ -590,8 +592,8 @@ impl Display for EditCommand { EditCommand::MoveViWordRightEnd { .. } => { write!(f, "MoveViWordRightEnd Optional[select: ]") } - EditCommand::MoveWordRightStart { .. } => { - write!(f, "MoveWordRightStart Optional[select: ]") + EditCommand::MoveEmacsWordRightStart { .. } => { + write!(f, "MoveEmacsWordRightStart Optional[select: ]") } EditCommand::MoveBigWordRightStart { .. } => { write!(f, "MoveBigWordRightStart Optional[select: ]") @@ -633,13 +635,13 @@ impl Display for EditCommand { } EditCommand::CutToLineEnd => write!(f, "CutToLineEnd"), EditCommand::KillLine => write!(f, "KillLine"), - EditCommand::CutWordLeft => write!(f, "CutWordLeft"), + EditCommand::CutEmacsWordLeft => write!(f, "CutEmacsWordLeft"), EditCommand::CutBigWordLeft => write!(f, "CutBigWordLeft"), - EditCommand::CutWordRight => write!(f, "CutWordRight"), + EditCommand::CutEmacsWordRight => write!(f, "CutEmacsWordRight"), EditCommand::CutBigWordRight => write!(f, "CutBigWordRight"), EditCommand::CutViWordRightEnd => write!(f, "CutViWordRightEnd"), EditCommand::CutViBigWordRightEnd => write!(f, "CutViBigWordRightEnd"), - EditCommand::CutWordRightToNext => write!(f, "CutWordRightToNext"), + EditCommand::CutEmacsWordRightToNext => write!(f, "CutEmacsWordRightToNext"), EditCommand::CutBigWordRightToNext => write!(f, "CutBigWordRightToNext"), EditCommand::CutViWordLeft => write!(f, "CutViWordLeft"), EditCommand::PasteCutBufferBefore => write!(f, "PasteCutBufferBefore"), @@ -670,14 +672,14 @@ impl Display for EditCommand { EditCommand::CopyToEndLinewise => write!(f, "CopyToEndLinewise"), EditCommand::CopyToLineEnd => write!(f, "CopyToLineEnd"), EditCommand::CopyCurrentLine => write!(f, "CopyCurrentLine"), - EditCommand::CopyWordLeft => write!(f, "CopyWordLeft"), + EditCommand::CopyEmacsWordLeft => write!(f, "CopyEmacsWordLeft"), EditCommand::CopyBigWordLeft => write!(f, "CopyBigWordLeft"), EditCommand::CopyViWordLeft => write!(f, "CopyViWordLeft"), - EditCommand::CopyWordRight => write!(f, "CopyWordRight"), + EditCommand::CopyEmacsWordRight => write!(f, "CopyEmacsWordRight"), EditCommand::CopyBigWordRight => write!(f, "CopyBigWordRight"), EditCommand::CopyViWordRightEnd => write!(f, "CopyViWordRightEnd"), EditCommand::CopyViBigWordRightEnd => write!(f, "CopyViBigWordRightEnd"), - EditCommand::CopyWordRightToNext => write!(f, "CopyWordRightToNext"), + EditCommand::CopyEmacsWordRightToNext => write!(f, "CopyEmacsWordRightToNext"), EditCommand::CopyBigWordRightToNext => write!(f, "CopyBigWordRightToNext"), EditCommand::CopyLeft => write!(f, "CopyLeft"), EditCommand::CopyRight => write!(f, "CopyRight"), @@ -718,12 +720,12 @@ impl EditCommand { | EditCommand::MoveLineDown { select, .. } | EditCommand::MoveLeft { select, .. } | EditCommand::MoveRight { select, .. } - | EditCommand::MoveWordLeft { select, .. } + | EditCommand::MoveEmacsWordLeft { select, .. } | EditCommand::MoveBigWordLeft { select, .. } - | EditCommand::MoveWordRight { select, .. } - | EditCommand::MoveWordRightStart { select, .. } + | EditCommand::MoveEmacsWordRight { select, .. } + | EditCommand::MoveEmacsWordRightStart { select, .. } | EditCommand::MoveBigWordRightStart { select, .. } - | EditCommand::MoveWordRightEnd { select, .. } + | EditCommand::MoveEmacsWordRightEnd { select, .. } | EditCommand::MoveBigWordRightEnd { select, .. } | EditCommand::MoveViWordLeft { select, .. } | EditCommand::MoveViWordRightStart { select, .. } @@ -762,13 +764,13 @@ impl EditCommand { | EditCommand::KillLine | EditCommand::CutToEnd | EditCommand::CutToEndLinewise { .. } - | EditCommand::CutWordLeft + | EditCommand::CutEmacsWordLeft | EditCommand::CutBigWordLeft - | EditCommand::CutWordRight + | EditCommand::CutEmacsWordRight | EditCommand::CutBigWordRight | EditCommand::CutViWordRightEnd | EditCommand::CutViBigWordRightEnd - | EditCommand::CutWordRightToNext + | EditCommand::CutEmacsWordRightToNext | EditCommand::CutBigWordRightToNext | EditCommand::CutViWordLeft | EditCommand::PasteCutBufferBefore @@ -805,14 +807,14 @@ impl EditCommand { | EditCommand::CopyToEndLinewise | EditCommand::CopyToLineEnd | EditCommand::CopyCurrentLine - | EditCommand::CopyWordLeft + | EditCommand::CopyEmacsWordLeft | EditCommand::CopyBigWordLeft | EditCommand::CopyViWordLeft - | EditCommand::CopyWordRight + | EditCommand::CopyEmacsWordRight | EditCommand::CopyBigWordRight | EditCommand::CopyViWordRightEnd | EditCommand::CopyViBigWordRightEnd - | EditCommand::CopyWordRightToNext + | EditCommand::CopyEmacsWordRightToNext | EditCommand::CopyBigWordRightToNext | EditCommand::CopyLeft | EditCommand::CopyRight