feat: add counted word change actions

Add cw/cb and configurable core actions using the same line-local spans as dw/db. Enter Insert with grouped replacement undo and native dot repeat.
This commit is contained in:
2026-10-13 17:36:15 +02:00
parent 983f43e710
commit e84d70463f
6 changed files with 140 additions and 15 deletions
+36 -7
View File
@@ -568,6 +568,16 @@ core_actions![
Action::DeleteWordBackward,
&[Mode::Normal]
),
(
"core.change_word_forward",
Action::ChangeWordForward,
&[Mode::Normal]
),
(
"core.change_word_backward",
Action::ChangeWordBackward,
&[Mode::Normal]
),
("core.paste_before", Action::PasteBefore, &[Mode::Normal]),
("core.paste_after", Action::PasteAfter, &[Mode::Normal]),
(
@@ -953,6 +963,8 @@ impl Action {
| Self::SubstituteCharacter
| Self::DeleteWordForward
| Self::DeleteWordBackward
| Self::ChangeWordForward
| Self::ChangeWordBackward
| Self::Cut
| Self::Change
| Self::PasteBefore
@@ -6106,6 +6118,8 @@ impl Editor {
}),
Input::Char('w') if operator == 'd' => Some(Action::DeleteWordForward),
Input::Char('b') if operator == 'd' => Some(Action::DeleteWordBackward),
Input::Char('w') if operator == 'c' => Some(Action::ChangeWordForward),
Input::Char('b') if operator == 'c' => Some(Action::ChangeWordBackward),
_ => None,
};
if let Some(action) = action {
@@ -6425,7 +6439,9 @@ impl Editor {
| Action::CopyToLineEnd
| Action::SubstituteCharacter
| Action::DeleteWordForward
| Action::DeleteWordBackward => self.edit_normal(action, count),
| Action::DeleteWordBackward
| Action::ChangeWordForward
| Action::ChangeWordBackward => self.edit_normal(action, count),
Action::LspCompletion => {
if self.active.view.mode == Mode::Insert {
return Some(Invocation {
@@ -8220,8 +8236,11 @@ impl Editor {
.map(|(index, target)| {
let cursor = target.active;
let end = match action {
Action::DeleteWordForward | Action::DeleteWordBackward => {
self.word_destination(cursor, action, count)
Action::DeleteWordForward | Action::ChangeWordForward => {
self.word_destination(cursor, Action::DeleteWordForward, count)
}
Action::DeleteWordBackward | Action::ChangeWordBackward => {
self.word_destination(cursor, Action::DeleteWordBackward, count)
}
Action::SubstituteCharacter => Cursor {
row: cursor.row,
@@ -8277,7 +8296,10 @@ impl Editor {
let start = EditStart::Normal { action, count };
if matches!(
action,
Action::ChangeToLineEnd | Action::SubstituteCharacter
Action::ChangeToLineEnd
| Action::SubstituteCharacter
| Action::ChangeWordForward
| Action::ChangeWordBackward
) {
self.start_insert(before, start);
self.edit_ranges(Action::Change, &ranges);
@@ -8940,7 +8962,10 @@ impl Editor {
clipboard = Some((self.selection_fragments(&ranges), false));
let change = matches!(
action,
Action::ChangeToLineEnd | Action::SubstituteCharacter
Action::ChangeToLineEnd
| Action::SubstituteCharacter
| Action::ChangeWordForward
| Action::ChangeWordBackward
);
self.edit_ranges(if change { Action::Change } else { Action::Cut }, &ranges);
if !change {
@@ -16024,7 +16049,9 @@ mod tests {
let carets = editor.carets();
let revision = editor.text_revision();
fixture.fail("fail-write", true);
for keys in ["yy", "2y3y", "2dd", "cc", "C", "D", "Y", "s", "d2w", "d2b"] {
for keys in [
"yy", "2y3y", "2dd", "cc", "C", "D", "Y", "s", "d2w", "d2b", "c2w", "c2b",
] {
type_text(&mut editor, keys);
assert_eq!(editor.context().text, "Xone\ntwo\nthree");
assert_eq!(editor.carets(), carets);
@@ -19673,6 +19700,8 @@ mod tests {
("4s", [(0, 0), (0, 2)], "!o three four\nkeep"),
("d2w", [(0, 0), (0, 4)], "four\nkeep"),
("d2b", [(0, 8), (0, 14)], "four\nkeep"),
("c2w", [(0, 0), (0, 4)], "!four\nkeep"),
("c2b", [(0, 8), (0, 14)], "!four\nkeep"),
("4rX", [(0, 0), (0, 2)], "XXXXXXo three four\nkeep"),
] {
let mut editor = editor();
@@ -19741,7 +19770,7 @@ mod tests {
let targets = editor.active.view.targets.clone();
let undo = editor.active.undo.len();
let clipboard = editor.clipboard.clone();
for keys in ["C", "D", "s", "2r", "d2w", "d2b"] {
for keys in ["C", "D", "s", "2r", "d2w", "d2b", "c2w", "c2b"] {
type_text(&mut editor, keys);
assert_eq!(editor.context().text, text, "{keys}");
assert_eq!(editor.active.view.targets, targets, "{keys}");