mirror of
https://github.com/openai/codex.git
synced 2026-10-28 18:58:46 +00:00
feat(tui): complete vim change operations
This commit is contained in:
@@ -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`).
|
||||
|
||||
@@ -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": [
|
||||
{
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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()),
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
|
||||
+1
-1
@@ -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
-2
@@ -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)
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user