Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions codex-rs/config/src/tui_keymap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,20 @@ pub struct TuiVimNormalKeymap {
pub move_line_start: Option<KeybindingsSpec>,
/// Move cursor to end of line (`$`).
pub move_line_end: Option<KeybindingsSpec>,
/// Begin a goto sequence (`g`, then `g` jumps to the top).
pub start_goto_sequence: Option<KeybindingsSpec>,
/// Move cursor to the end of the text (`G`).
pub jump_bottom: Option<KeybindingsSpec>,
/// Enter visual mode (`v`).
pub enter_visual: Option<KeybindingsSpec>,
/// Enter linewise visual mode (`V`).
pub enter_visual_line: Option<KeybindingsSpec>,
/// Undo the previous text edit (`u`).
pub undo: Option<KeybindingsSpec>,
/// Redo the previous undone edit (`Ctrl-R`).
pub redo: Option<KeybindingsSpec>,
/// Repeat the previous Vim normal-mode edit (`.`).
pub repeat_last_edit: Option<KeybindingsSpec>,
/// Delete character under cursor (`x`).
pub delete_char: Option<KeybindingsSpec>,
/// Delete character under cursor and enter insert mode (`s`).
Expand All @@ -245,10 +259,10 @@ 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
/// `Esc` cancels the pending operator and returns to normal mode without
/// modifying text.
/// This context is active only while waiting for a motion after `d`, `y`, or
/// `c`. Repeating delete/yank (`dd`, `yy`) targets the entire line; change uses
/// the normal-mode change key (`cc`). Pressing `Esc` cancels the pending
/// operator and returns to normal mode without modifying text.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
#[schemars(deny_unknown_fields)]
pub struct TuiVimOperatorKeymap {
Expand Down
72 changes: 71 additions & 1 deletion codex-rs/core/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2842,7 +2842,10 @@
"delete_char": null,
"delete_to_line_end": null,
"enter_insert": null,
"enter_visual": null,
"enter_visual_line": null,
"insert_line_start": null,
"jump_bottom": null,
"move_down": null,
"move_left": null,
"move_line_end": null,
Expand All @@ -2855,10 +2858,14 @@
"open_line_above": null,
"open_line_below": null,
"paste_after": null,
"redo": null,
"repeat_last_edit": null,
"start_change_operator": null,
"start_delete_operator": null,
"start_goto_sequence": null,
"start_yank_operator": null,
"substitute_char": null,
"undo": null,
"yank_line": null
},
"vim_operator": {
Expand Down Expand Up @@ -3533,7 +3540,10 @@
"delete_char": null,
"delete_to_line_end": null,
"enter_insert": null,
"enter_visual": null,
"enter_visual_line": null,
"insert_line_start": null,
"jump_bottom": null,
"move_down": null,
"move_left": null,
"move_line_end": null,
Expand All @@ -3546,10 +3556,14 @@
"open_line_above": null,
"open_line_below": null,
"paste_after": null,
"redo": null,
"repeat_last_edit": null,
"start_change_operator": null,
"start_delete_operator": null,
"start_goto_sequence": null,
"start_yank_operator": null,
"substitute_char": null,
"undo": null,
"yank_line": null
}
},
Expand Down Expand Up @@ -3849,6 +3863,22 @@
],
"description": "Enter insert mode at cursor (`i`)."
},
"enter_visual": {
"allOf": [
{
"$ref": "#/definitions/KeybindingsSpec"
}
],
"description": "Enter visual mode (`v`)."
},
"enter_visual_line": {
"allOf": [
{
"$ref": "#/definitions/KeybindingsSpec"
}
],
"description": "Enter linewise visual mode (`V`)."
},
"insert_line_start": {
"allOf": [
{
Expand All @@ -3857,6 +3887,14 @@
],
"description": "Enter insert mode at first non-blank of line (`I`)."
},
"jump_bottom": {
"allOf": [
{
"$ref": "#/definitions/KeybindingsSpec"
}
],
"description": "Move cursor to the end of the text (`G`)."
},
"move_down": {
"allOf": [
{
Expand Down Expand Up @@ -3953,6 +3991,22 @@
],
"description": "Paste after cursor (`p`)."
},
"redo": {
"allOf": [
{
"$ref": "#/definitions/KeybindingsSpec"
}
],
"description": "Redo the previous undone edit (`Ctrl-R`)."
},
"repeat_last_edit": {
"allOf": [
{
"$ref": "#/definitions/KeybindingsSpec"
}
],
"description": "Repeat the previous Vim normal-mode edit (`.`)."
},
"start_change_operator": {
"allOf": [
{
Expand All @@ -3969,6 +4023,14 @@
],
"description": "Begin delete operator; next key selects motion (`d`)."
},
"start_goto_sequence": {
"allOf": [
{
"$ref": "#/definitions/KeybindingsSpec"
}
],
"description": "Begin a goto sequence (`g`, then `g` jumps to the top)."
},
"start_yank_operator": {
"allOf": [
{
Expand All @@ -3985,6 +4047,14 @@
],
"description": "Delete character under cursor and enter insert mode (`s`)."
},
"undo": {
"allOf": [
{
"$ref": "#/definitions/KeybindingsSpec"
}
],
"description": "Undo the previous text edit (`u`)."
},
"yank_line": {
"allOf": [
{
Expand All @@ -3998,7 +4068,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 delete/yank (`dd`, `yy`) targets the entire line; change uses the normal-mode change key (`cc`). Pressing `Esc` cancels the pending operator and returns to normal mode without modifying text.",
"properties": {
"cancel": {
"allOf": [
Expand Down
30 changes: 30 additions & 0 deletions codex-rs/tui/src/bottom_pane/chat_composer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1080,6 +1080,8 @@ impl ChatComposer {
.vim_mode_label()
.map(|label| match label {
"Normal" => "Vim: Normal".magenta(),
"Visual" => "Vim: Visual".cyan(),
"Visual Line" => "Vim: Visual Line".cyan(),
"Insert" => "Vim: Insert".green(),
_ => unreachable!(),
})
Expand Down Expand Up @@ -5397,6 +5399,34 @@ mod tests {
assert!(!composer.footer.esc_backtrack_hint);
}

#[test]
fn vim_visual_mode_indicator_renders_without_panic() {
use crossterm::event::KeyCode;
use crossterm::event::KeyEvent;
use crossterm::event::KeyModifiers;

let (tx, _rx) = unbounded_channel::<AppEvent>();
let sender = AppEventSender::new(tx);
let mut composer = ChatComposer::new(
/*has_input_focus*/ true,
sender,
/*enhanced_keys_supported*/ true,
"Ask Codex to do anything".to_string(),
/*disable_paste_burst*/ false,
);
composer.set_vim_enabled(/*enabled*/ true);

let (result, needs_redraw) =
composer.handle_key_event(KeyEvent::new(KeyCode::Char('v'), KeyModifiers::NONE));

assert!(matches!(result, InputResult::None));
assert!(needs_redraw);
assert_eq!(
composer.vim_mode_indicator_span(),
Some("Vim: Visual".cyan())
);
}

#[test]
fn slash_opens_command_popup_in_vim_normal_mode() {
use crossterm::event::KeyCode;
Expand Down
Loading
Loading