feat(tui): complete vim change operations

This commit is contained in:
Felipe Coury
2026-05-25 16:42:09 -03:00
parent ac5067f921
commit e98e93e277
10 changed files with 253 additions and 33 deletions
+9 -3
View File
@@ -225,6 +225,10 @@ pub struct TuiVimNormalKeymap {
pub delete_to_line_end: Option<KeybindingsSpec>,
/// Change from cursor to end of line and enter insert mode (`C`).
pub change_to_line_end: Option<KeybindingsSpec>,
/// Substitute the character under the cursor and enter insert mode (`s`).
pub substitute_char: Option<KeybindingsSpec>,
/// Substitute the current line and enter insert mode (`S`).
pub substitute_line: Option<KeybindingsSpec>,
/// Yank the entire line (`Y`).
pub yank_line: Option<KeybindingsSpec>,
/// Paste after cursor (`p`).
@@ -233,7 +237,7 @@ pub struct TuiVimNormalKeymap {
pub start_delete_operator: Option<KeybindingsSpec>,
/// Begin yank operator; next key selects motion (`y`).
pub start_yank_operator: Option<KeybindingsSpec>,
/// Begin change operator; next keys select a text object.
/// Begin change operator; next keys select a motion or text object.
pub start_change_operator: Option<KeybindingsSpec>,
/// Cancel a pending operator and return to normal mode.
pub cancel_operator: Option<KeybindingsSpec>,
@@ -241,8 +245,8 @@ pub struct TuiVimNormalKeymap {
/// Vim operator-pending keybindings for modal editing inside text areas.
///
/// This context is active only while waiting for a motion after `d` or `y`.
/// Repeating the operator key (`dd`, `yy`) targets the entire line. Pressing
/// This context is active only while waiting for a motion after `d`, `y`, or `c`.
/// Repeating the operator key (`dd`, `yy`, `cc`) targets the entire line. Pressing
/// `Esc` cancels the pending operator and returns to normal mode without
/// modifying text.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
@@ -252,6 +256,8 @@ pub struct TuiVimOperatorKeymap {
pub delete_line: Option<KeybindingsSpec>,
/// Repeat yank operator to yank the whole line (`yy`).
pub yank_line: Option<KeybindingsSpec>,
/// Repeat change operator to change the whole line (`cc`).
pub change_line: Option<KeybindingsSpec>,
/// Motion: left (`h`).
pub motion_left: Option<KeybindingsSpec>,
/// Motion: right (`l`).
+32 -2
View File
@@ -2844,10 +2844,13 @@
"start_change_operator": null,
"start_delete_operator": null,
"start_yank_operator": null,
"substitute_char": null,
"substitute_line": null,
"yank_line": null
},
"vim_operator": {
"cancel": null,
"change_line": null,
"delete_line": null,
"motion_down": null,
"motion_left": null,
@@ -3525,6 +3528,8 @@
"start_change_operator": null,
"start_delete_operator": null,
"start_yank_operator": null,
"substitute_char": null,
"substitute_line": null,
"yank_line": null
}
},
@@ -3536,6 +3541,7 @@
],
"default": {
"cancel": null,
"change_line": null,
"delete_line": null,
"motion_down": null,
"motion_left": null,
@@ -3934,7 +3940,7 @@
"$ref": "#/definitions/KeybindingsSpec"
}
],
"description": "Begin change operator; next keys select a text object."
"description": "Begin change operator; next keys select a motion or text object."
},
"start_delete_operator": {
"allOf": [
@@ -3952,6 +3958,22 @@
],
"description": "Begin yank operator; next key selects motion (`y`)."
},
"substitute_char": {
"allOf": [
{
"$ref": "#/definitions/KeybindingsSpec"
}
],
"description": "Substitute the character under the cursor and enter insert mode (`s`)."
},
"substitute_line": {
"allOf": [
{
"$ref": "#/definitions/KeybindingsSpec"
}
],
"description": "Substitute the current line and enter insert mode (`S`)."
},
"yank_line": {
"allOf": [
{
@@ -3965,7 +3987,7 @@
},
"TuiVimOperatorKeymap": {
"additionalProperties": false,
"description": "Vim operator-pending keybindings for modal editing inside text areas.\n\nThis context is active only while waiting for a motion after `d` or `y`. Repeating the operator key (`dd`, `yy`) targets the entire line. Pressing `Esc` cancels the pending operator and returns to normal mode without modifying text.",
"description": "Vim operator-pending keybindings for modal editing inside text areas.\n\nThis context is active only while waiting for a motion after `d`, `y`, or `c`. Repeating the operator key (`dd`, `yy`, `cc`) targets the entire line. Pressing `Esc` cancels the pending operator and returns to normal mode without modifying text.",
"properties": {
"cancel": {
"allOf": [
@@ -3975,6 +3997,14 @@
],
"description": "Cancel the pending operator and return to normal mode."
},
"change_line": {
"allOf": [
{
"$ref": "#/definitions/KeybindingsSpec"
}
],
"description": "Repeat change operator to change the whole line (`cc`)."
},
"delete_line": {
"allOf": [
{
+125 -19
View File
@@ -740,10 +740,18 @@ impl TextArea {
return;
}
if self.vim_normal_keymap.change_to_line_end.is_pressed(event) {
self.vim_kill_to_end_of_line();
self.apply_vim_operator(VimOperator::Change, VimMotion::LineEnd);
return;
}
if self.vim_normal_keymap.substitute_char.is_pressed(event) {
self.delete_forward_kill(/*n*/ 1);
self.vim_mode = VimMode::Insert;
return;
}
if self.vim_normal_keymap.substitute_line.is_pressed(event) {
self.change_current_line();
return;
}
if self.vim_normal_keymap.yank_line.is_pressed(event) {
self.yank_current_line();
return;
@@ -786,6 +794,10 @@ impl TextArea {
self.yank_current_line();
return true;
}
if op == VimOperator::Change && self.vim_operator_keymap.change_line.is_pressed(event) {
self.change_current_line();
return true;
}
if self.vim_operator_keymap.cancel.is_pressed(event) {
return true;
}
@@ -797,9 +809,7 @@ impl TextArea {
return true;
}
if op != VimOperator::Change
&& let Some(motion) = self.vim_motion_for_event(event)
{
if let Some(motion) = self.vim_motion_for_event(event) {
self.apply_vim_operator(op, motion);
return true;
}
@@ -864,16 +874,41 @@ impl TextArea {
}
fn apply_vim_operator(&mut self, op: VimOperator, motion: VimMotion) {
let Some(range) = self.range_for_motion(motion) else {
return;
};
match op {
VimOperator::Delete => self.kill_range(range),
VimOperator::Yank => self.yank_range(range),
VimOperator::Change => {}
VimOperator::Delete => {
if let Some(range) = self.range_for_motion(motion) {
self.kill_range(range);
}
}
VimOperator::Yank => {
if let Some(range) = self.range_for_motion(motion) {
self.yank_range(range);
}
}
VimOperator::Change => {
if let Some(range) = self.range_for_change_motion(motion) {
self.kill_range(range);
self.vim_mode = VimMode::Insert;
} else if motion == VimMotion::LineEnd {
self.vim_mode = VimMode::Insert;
}
}
}
}
fn range_for_change_motion(&mut self, motion: VimMotion) -> Option<Range<usize>> {
if motion == VimMotion::WordForward
&& self.text[self.cursor_pos..]
.chars()
.next()
.is_some_and(|ch| !ch.is_whitespace())
{
let end = self.end_of_next_word();
return (self.cursor_pos < end).then_some(self.cursor_pos..end);
}
self.range_for_motion(motion)
}
fn apply_vim_operator_to_range(&mut self, op: VimOperator, range: Range<usize>) {
match op {
VimOperator::Delete => self.kill_range(range),
@@ -1166,6 +1201,12 @@ impl TextArea {
self.kill_line_range(range);
}
fn change_current_line(&mut self) {
let range = self.beginning_of_current_line()..self.end_of_current_line();
self.kill_line_range(range);
self.vim_mode = VimMode::Insert;
}
fn current_line_range_with_newline(&self) -> Range<usize> {
let bol = self.beginning_of_current_line();
let eol = self.end_of_current_line();
@@ -2443,6 +2484,79 @@ mod tests {
assert_eq!(t.vim_mode_label(), Some("Insert"));
}
#[test]
fn vim_change_motion_and_linewise_aliases_enter_insert_mode() {
let mut t = ta_with("hello world");
t.set_cursor(/*pos*/ 0);
t.set_vim_enabled(/*enabled*/ true);
t.input(KeyEvent::new(KeyCode::Char('c'), KeyModifiers::NONE));
t.input(KeyEvent::new(KeyCode::Char('w'), KeyModifiers::NONE));
assert_eq!(t.text(), " world");
assert_eq!(t.kill_buffer, "hello");
assert_eq!(t.vim_mode_label(), Some("Insert"));
let mut t = ta_with("hello world\nnext line");
t.set_cursor(/*pos*/ 6);
t.set_vim_enabled(/*enabled*/ true);
t.input(KeyEvent::new(KeyCode::Char('c'), KeyModifiers::NONE));
t.input(KeyEvent::new(KeyCode::Char('$'), KeyModifiers::NONE));
assert_eq!(t.text(), "hello \nnext line");
assert_eq!(t.kill_buffer, "world");
assert_eq!(t.vim_mode_label(), Some("Insert"));
let mut t = ta_with("one\ntwo\nthree");
t.set_cursor(/*pos*/ "one\nt".len());
t.set_vim_enabled(/*enabled*/ true);
t.input(KeyEvent::new(KeyCode::Char('c'), KeyModifiers::NONE));
t.input(KeyEvent::new(KeyCode::Char('c'), KeyModifiers::NONE));
assert_eq!(t.text(), "one\n\nthree");
assert_eq!(t.kill_buffer, "two");
assert_eq!(t.kill_buffer_kind, KillBufferKind::Linewise);
assert_eq!(t.cursor(), "one\n".len());
assert_eq!(t.vim_mode_label(), Some("Insert"));
let mut t = ta_with("one\ntwo\nthree");
t.set_cursor(/*pos*/ "one\nt".len());
t.set_vim_enabled(/*enabled*/ true);
t.input(KeyEvent::new(KeyCode::Char('S'), KeyModifiers::NONE));
assert_eq!(t.text(), "one\n\nthree");
assert_eq!(t.kill_buffer, "two");
assert_eq!(t.vim_mode_label(), Some("Insert"));
}
#[test]
fn vim_substitute_char_preserves_grapheme_and_atomic_boundaries() {
let mut t = ta_with("👍 ok");
t.set_cursor(/*pos*/ 0);
t.set_vim_enabled(/*enabled*/ true);
t.input(KeyEvent::new(KeyCode::Char('s'), KeyModifiers::NONE));
assert_eq!(t.text(), " ok");
assert_eq!(t.kill_buffer, "👍");
assert_eq!(t.vim_mode_label(), Some("Insert"));
let mut t = ta_with("@file rest");
t.add_element_range(0.."@file".len())
.expect("valid element");
t.set_cursor(/*pos*/ 0);
t.set_vim_enabled(/*enabled*/ true);
t.input(KeyEvent::new(KeyCode::Char('s'), KeyModifiers::NONE));
assert_eq!(t.text(), " rest");
assert_eq!(t.kill_buffer, "@file");
assert_eq!(t.vim_mode_label(), Some("Insert"));
}
#[test]
fn vim_word_text_objects_cover_delete_yank_and_big_word() {
let mut t = ta_with("hello world");
@@ -2578,19 +2692,11 @@ mod tests {
}
#[test]
fn vim_text_object_cancellation_and_unsupported_change_motions_do_not_edit() {
fn vim_text_object_cancellation_does_not_edit() {
let mut t = ta_with("hello world");
t.set_cursor(/*pos*/ 1);
t.set_vim_enabled(/*enabled*/ true);
t.input(KeyEvent::new(KeyCode::Char('c'), KeyModifiers::NONE));
t.input(KeyEvent::new(KeyCode::Char('$'), KeyModifiers::NONE));
assert_eq!(t.text(), "hello world");
assert_eq!(t.kill_buffer, "");
assert_eq!(t.vim_mode_label(), Some("Normal"));
assert!(!t.is_vim_operator_pending());
t.input(KeyEvent::new(KeyCode::Char('d'), KeyModifiers::NONE));
t.input(KeyEvent::new(KeyCode::Char('i'), KeyModifiers::NONE));
assert!(t.is_vim_operator_pending());
+71 -2
View File
@@ -134,7 +134,7 @@ pub(crate) struct EditorKeymap {
///
/// Normal mode is the resting state when Vim is enabled. Pressing a movement
/// or editing key here either moves the cursor, triggers an operator-pending
/// state (via `start_delete_operator` / `start_yank_operator`), or transitions
/// state (via an operator-start action), or transitions
/// to insert mode. Default bindings include both `shift(letter)` and
/// `plain(UPPERCASE)` variants for uppercase commands like `A`, `I`, `O` to
/// handle cross-terminal shift-reporting inconsistencies.
@@ -158,6 +158,8 @@ pub(crate) struct VimNormalKeymap {
pub(crate) delete_char: Vec<KeyBinding>,
pub(crate) delete_to_line_end: Vec<KeyBinding>,
pub(crate) change_to_line_end: Vec<KeyBinding>,
pub(crate) substitute_char: Vec<KeyBinding>,
pub(crate) substitute_line: Vec<KeyBinding>,
pub(crate) yank_line: Vec<KeyBinding>,
pub(crate) paste_after: Vec<KeyBinding>,
pub(crate) start_delete_operator: Vec<KeyBinding>,
@@ -166,7 +168,7 @@ pub(crate) struct VimNormalKeymap {
pub(crate) cancel_operator: Vec<KeyBinding>,
}
/// Vim operator-pending keybindings active after `d` or `y` in normal mode.
/// Vim operator-pending keybindings active after `d`, `y`, or `c` in normal mode.
///
/// When an operator (`start_delete_operator` or `start_yank_operator`) is
/// pressed, the next keypress is matched against this context to determine the
@@ -176,6 +178,7 @@ pub(crate) struct VimNormalKeymap {
pub(crate) struct VimOperatorKeymap {
pub(crate) delete_line: Vec<KeyBinding>,
pub(crate) yank_line: Vec<KeyBinding>,
pub(crate) change_line: Vec<KeyBinding>,
pub(crate) motion_left: Vec<KeyBinding>,
pub(crate) motion_right: Vec<KeyBinding>,
pub(crate) motion_up: Vec<KeyBinding>,
@@ -489,6 +492,8 @@ impl RuntimeKeymap {
delete_char: resolve_local!(keymap, defaults, vim_normal, delete_char),
delete_to_line_end: resolve_local!(keymap, defaults, vim_normal, delete_to_line_end),
change_to_line_end: resolve_local!(keymap, defaults, vim_normal, change_to_line_end),
substitute_char: resolve_local!(keymap, defaults, vim_normal, substitute_char),
substitute_line: resolve_local!(keymap, defaults, vim_normal, substitute_line),
yank_line: resolve_local!(keymap, defaults, vim_normal, yank_line),
paste_after: resolve_local!(keymap, defaults, vim_normal, paste_after),
start_delete_operator: resolve_local!(
@@ -576,6 +581,18 @@ impl RuntimeKeymap {
keymap.vim_normal.delete_to_line_end.as_ref(),
vim_normal.delete_to_line_end.as_slice(),
),
(
keymap.vim_normal.change_to_line_end.as_ref(),
vim_normal.change_to_line_end.as_slice(),
),
(
keymap.vim_normal.substitute_char.as_ref(),
vim_normal.substitute_char.as_slice(),
),
(
keymap.vim_normal.substitute_line.as_ref(),
vim_normal.substitute_line.as_slice(),
),
(
keymap.vim_normal.yank_line.as_ref(),
vim_normal.yank_line.as_slice(),
@@ -603,10 +620,21 @@ impl RuntimeKeymap {
.start_change_operator
.retain(|binding| !configured_vim_normal_bindings_to_preserve.contains(binding));
}
if keymap.vim_normal.substitute_char.is_none() {
vim_normal
.substitute_char
.retain(|binding| !configured_vim_normal_bindings_to_preserve.contains(binding));
}
if keymap.vim_normal.substitute_line.is_none() {
vim_normal
.substitute_line
.retain(|binding| !configured_vim_normal_bindings_to_preserve.contains(binding));
}
let mut vim_operator = VimOperatorKeymap {
delete_line: resolve_local!(keymap, defaults, vim_operator, delete_line),
yank_line: resolve_local!(keymap, defaults, vim_operator, yank_line),
change_line: resolve_local!(keymap, defaults, vim_operator, change_line),
motion_left: resolve_local!(keymap, defaults, vim_operator, motion_left),
motion_right: resolve_local!(keymap, defaults, vim_operator, motion_right),
motion_up: resolve_local!(keymap, defaults, vim_operator, motion_up),
@@ -650,6 +678,10 @@ impl RuntimeKeymap {
keymap.vim_operator.yank_line.as_ref(),
vim_operator.yank_line.as_slice(),
),
(
keymap.vim_operator.change_line.as_ref(),
vim_operator.change_line.as_slice(),
),
(
keymap.vim_operator.motion_left.as_ref(),
vim_operator.motion_left.as_slice(),
@@ -702,6 +734,11 @@ impl RuntimeKeymap {
.select_around_text_object
.retain(|binding| !configured_vim_operator_bindings_to_preserve.contains(binding));
}
if keymap.vim_operator.change_line.is_none() {
vim_operator
.change_line
.retain(|binding| !configured_vim_operator_bindings_to_preserve.contains(binding));
}
let vim_text_object = VimTextObjectKeymap {
word: resolve_local!(keymap, defaults, vim_text_object, word),
@@ -990,6 +1027,11 @@ impl RuntimeKeymap {
shift(KeyCode::Char('c')),
plain(KeyCode::Char('C'))
],
substitute_char: default_bindings![plain(KeyCode::Char('s'))],
substitute_line: default_bindings![
shift(KeyCode::Char('s')),
plain(KeyCode::Char('S'))
],
yank_line: default_bindings![shift(KeyCode::Char('y')), plain(KeyCode::Char('Y'))],
paste_after: default_bindings![plain(KeyCode::Char('p'))],
start_delete_operator: default_bindings![plain(KeyCode::Char('d'))],
@@ -1000,6 +1042,7 @@ impl RuntimeKeymap {
vim_operator: VimOperatorKeymap {
delete_line: default_bindings![plain(KeyCode::Char('d'))],
yank_line: default_bindings![plain(KeyCode::Char('y'))],
change_line: default_bindings![plain(KeyCode::Char('c'))],
motion_left: default_bindings![plain(KeyCode::Char('h'))],
motion_right: default_bindings![plain(KeyCode::Char('l'))],
motion_up: default_bindings![plain(KeyCode::Char('k'))],
@@ -1414,6 +1457,14 @@ impl RuntimeKeymap {
"change_to_line_end",
self.vim_normal.change_to_line_end.as_slice(),
),
(
"substitute_char",
self.vim_normal.substitute_char.as_slice(),
),
(
"substitute_line",
self.vim_normal.substitute_line.as_slice(),
),
("yank_line", self.vim_normal.yank_line.as_slice()),
("paste_after", self.vim_normal.paste_after.as_slice()),
(
@@ -1440,6 +1491,7 @@ impl RuntimeKeymap {
[
("delete_line", self.vim_operator.delete_line.as_slice()),
("yank_line", self.vim_operator.yank_line.as_slice()),
("change_line", self.vim_operator.change_line.as_slice()),
("motion_left", self.vim_operator.motion_left.as_slice()),
("motion_right", self.vim_operator.motion_right.as_slice()),
("motion_up", self.vim_operator.motion_up.as_slice()),
@@ -2268,6 +2320,23 @@ mod tests {
expect_conflict(&keymap, "move_left", "start_change_operator");
}
#[test]
fn configured_legacy_vim_bindings_prune_new_substitute_and_change_line_defaults() {
let mut keymap = TuiKeymap::default();
keymap.vim_normal.move_left = Some(one("s"));
keymap.vim_normal.move_right = Some(one("shift-s"));
keymap.vim_operator.motion_left = Some(one("c"));
let runtime = RuntimeKeymap::from_config(&keymap).expect("config should parse");
assert_eq!(runtime.vim_normal.substitute_char, Vec::new());
assert_eq!(
runtime.vim_normal.substitute_line,
vec![key_hint::plain(KeyCode::Char('S'))]
);
assert_eq!(runtime.vim_operator.change_line, Vec::new());
}
#[test]
fn configured_legacy_vim_operator_bindings_prune_new_text_object_defaults() {
let mut keymap = TuiKeymap::default();
+10 -1
View File
@@ -136,14 +136,17 @@ pub(super) const KEYMAP_ACTIONS: &[KeymapActionDescriptor] = &[
action("vim_normal", "Vim normal", "delete_char", "Delete the character under the cursor."),
action("vim_normal", "Vim normal", "delete_to_line_end", "Delete from cursor to end of line."),
action("vim_normal", "Vim normal", "change_to_line_end", "Change from cursor to end of line and enter insert mode."),
action("vim_normal", "Vim normal", "substitute_char", "Substitute the character under the cursor and enter insert mode."),
action("vim_normal", "Vim normal", "substitute_line", "Substitute the current line and enter insert mode."),
action("vim_normal", "Vim normal", "yank_line", "Yank the entire line."),
action("vim_normal", "Vim normal", "paste_after", "Paste after the cursor."),
action("vim_normal", "Vim normal", "start_delete_operator", "Begin a delete operator and wait for a motion."),
action("vim_normal", "Vim normal", "start_yank_operator", "Begin a yank operator and wait for a motion."),
action("vim_normal", "Vim normal", "start_change_operator", "Begin a change operator and wait for a text object."),
action("vim_normal", "Vim normal", "start_change_operator", "Begin a change operator and wait for a motion or text object."),
action("vim_normal", "Vim normal", "cancel_operator", "Cancel a pending Vim operator."),
action("vim_operator", "Vim operator", "delete_line", "Repeat delete operator to delete the whole line."),
action("vim_operator", "Vim operator", "yank_line", "Repeat yank operator to yank the whole line."),
action("vim_operator", "Vim operator", "change_line", "Repeat change operator to change the whole line."),
action("vim_operator", "Vim operator", "motion_left", "Operator motion left."),
action("vim_operator", "Vim operator", "motion_right", "Operator motion right."),
action("vim_operator", "Vim operator", "motion_up", "Operator motion up."),
@@ -277,6 +280,8 @@ pub(super) fn binding_slot<'a>(
("vim_normal", "delete_char") => Some(&mut keymap.vim_normal.delete_char),
("vim_normal", "delete_to_line_end") => Some(&mut keymap.vim_normal.delete_to_line_end),
("vim_normal", "change_to_line_end") => Some(&mut keymap.vim_normal.change_to_line_end),
("vim_normal", "substitute_char") => Some(&mut keymap.vim_normal.substitute_char),
("vim_normal", "substitute_line") => Some(&mut keymap.vim_normal.substitute_line),
("vim_normal", "yank_line") => Some(&mut keymap.vim_normal.yank_line),
("vim_normal", "paste_after") => Some(&mut keymap.vim_normal.paste_after),
("vim_normal", "start_delete_operator") => Some(&mut keymap.vim_normal.start_delete_operator),
@@ -285,6 +290,7 @@ pub(super) fn binding_slot<'a>(
("vim_normal", "cancel_operator") => Some(&mut keymap.vim_normal.cancel_operator),
("vim_operator", "delete_line") => Some(&mut keymap.vim_operator.delete_line),
("vim_operator", "yank_line") => Some(&mut keymap.vim_operator.yank_line),
("vim_operator", "change_line") => Some(&mut keymap.vim_operator.change_line),
("vim_operator", "motion_left") => Some(&mut keymap.vim_operator.motion_left),
("vim_operator", "motion_right") => Some(&mut keymap.vim_operator.motion_right),
("vim_operator", "motion_up") => Some(&mut keymap.vim_operator.motion_up),
@@ -400,6 +406,8 @@ pub(super) fn bindings_for_action<'a>(
("vim_normal", "delete_char") => Some(runtime_keymap.vim_normal.delete_char.as_slice()),
("vim_normal", "delete_to_line_end") => Some(runtime_keymap.vim_normal.delete_to_line_end.as_slice()),
("vim_normal", "change_to_line_end") => Some(runtime_keymap.vim_normal.change_to_line_end.as_slice()),
("vim_normal", "substitute_char") => Some(runtime_keymap.vim_normal.substitute_char.as_slice()),
("vim_normal", "substitute_line") => Some(runtime_keymap.vim_normal.substitute_line.as_slice()),
("vim_normal", "yank_line") => Some(runtime_keymap.vim_normal.yank_line.as_slice()),
("vim_normal", "paste_after") => Some(runtime_keymap.vim_normal.paste_after.as_slice()),
("vim_normal", "start_delete_operator") => Some(runtime_keymap.vim_normal.start_delete_operator.as_slice()),
@@ -408,6 +416,7 @@ pub(super) fn bindings_for_action<'a>(
("vim_normal", "cancel_operator") => Some(runtime_keymap.vim_normal.cancel_operator.as_slice()),
("vim_operator", "delete_line") => Some(runtime_keymap.vim_operator.delete_line.as_slice()),
("vim_operator", "yank_line") => Some(runtime_keymap.vim_operator.yank_line.as_slice()),
("vim_operator", "change_line") => Some(runtime_keymap.vim_operator.change_line.as_slice()),
("vim_operator", "motion_left") => Some(runtime_keymap.vim_operator.motion_left.as_slice()),
("vim_operator", "motion_right") => Some(runtime_keymap.vim_operator.motion_right.as_slice()),
("vim_operator", "motion_up") => Some(runtime_keymap.vim_operator.motion_up.as_slice()),
@@ -5,7 +5,7 @@ expression: "render_picker(params, 120)"
Keymap
All configurable shortcuts.
106 actions, 1 customized, 2 unbound.
109 actions, 1 customized, 2 unbound.
[All] Common Customized (1) Unbound (2) App Composer Editor Vim Navigation Approval Debug
@@ -5,7 +5,7 @@ expression: "render_picker(params, 120)"
Keymap
All configurable shortcuts.
107 actions, 0 customized, 3 unbound.
110 actions, 0 customized, 3 unbound.
[All] Common Customized (0) Unbound (3) App Composer Editor Vim Navigation Approval Debug
@@ -2,14 +2,14 @@
source: tui/src/keymap_setup.rs
expression: snapshot
---
tab: All (106 selectable)
tab: All (109 selectable)
tab: Common (19 selectable)
tab: Customized (0) (0 selectable)
tab: Unbound (2) (2 selectable)
tab: App (9 selectable)
tab: Composer (5 selectable)
tab: Editor (17 selectable)
tab: Vim (47 selectable)
tab: Vim (50 selectable)
tab: Navigation (20 selectable)
tab: Approval (8 selectable)
tab: Debug (1 selectable)
@@ -5,7 +5,7 @@ expression: "render_picker(params, 78)"
Keymap
All configurable shortcuts.
106 actions, 0 customized, 2 unbound.
109 actions, 0 customized, 2 unbound.
[All] Common Customized (0) Unbound (2) App Composer Editor Vim
Navigation Approval Debug
@@ -5,7 +5,7 @@ expression: "render_picker(params, 120)"
Keymap
All configurable shortcuts.
106 actions, 0 customized, 2 unbound.
109 actions, 0 customized, 2 unbound.
[All] Common Customized (0) Unbound (2) App Composer Editor Vim Navigation Approval Debug