mirror of
https://github.com/openai/codex.git
synced 2026-11-01 19:33:35 +00:00
docs(tui): config and keybinding docs
This commit is contained in:
@@ -175,48 +175,91 @@ pub struct TuiEditorKeymap {
|
||||
}
|
||||
|
||||
/// Vim normal-mode keybindings for modal editing inside text areas.
|
||||
///
|
||||
/// Actions that use uppercase letters (like `A` for append-line-end) should
|
||||
/// be specified as `shift-a` in config; the runtime matcher handles
|
||||
/// cross-terminal shift-reporting differences automatically.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
|
||||
#[schemars(deny_unknown_fields)]
|
||||
pub struct TuiVimNormalKeymap {
|
||||
/// Enter insert mode at cursor (`i`).
|
||||
pub enter_insert: Option<KeybindingsSpec>,
|
||||
/// Enter insert mode after cursor (`a`).
|
||||
pub append_after_cursor: Option<KeybindingsSpec>,
|
||||
/// Enter insert mode at end of line (`A`).
|
||||
pub append_line_end: Option<KeybindingsSpec>,
|
||||
/// Enter insert mode at first non-blank of line (`I`).
|
||||
pub insert_line_start: Option<KeybindingsSpec>,
|
||||
/// Open a new line below and enter insert mode (`o`).
|
||||
pub open_line_below: Option<KeybindingsSpec>,
|
||||
/// Open a new line above and enter insert mode (`O`).
|
||||
pub open_line_above: Option<KeybindingsSpec>,
|
||||
/// Move cursor left (`h`).
|
||||
pub move_left: Option<KeybindingsSpec>,
|
||||
/// Move cursor right (`l`).
|
||||
pub move_right: Option<KeybindingsSpec>,
|
||||
/// Move cursor up (`k`).
|
||||
pub move_up: Option<KeybindingsSpec>,
|
||||
/// Move cursor down (`j`).
|
||||
pub move_down: Option<KeybindingsSpec>,
|
||||
/// Move cursor to start of next word (`w`).
|
||||
pub move_word_forward: Option<KeybindingsSpec>,
|
||||
/// Move cursor to start of previous word (`b`).
|
||||
pub move_word_backward: Option<KeybindingsSpec>,
|
||||
/// Move cursor to end of current/next word (`e`).
|
||||
pub move_word_end: Option<KeybindingsSpec>,
|
||||
/// Move cursor to start of line (`0`).
|
||||
pub move_line_start: Option<KeybindingsSpec>,
|
||||
/// Move cursor to end of line (`$`).
|
||||
pub move_line_end: Option<KeybindingsSpec>,
|
||||
/// Delete character under cursor (`x`).
|
||||
pub delete_char: Option<KeybindingsSpec>,
|
||||
/// Delete from cursor to end of line (`D`).
|
||||
pub delete_to_line_end: Option<KeybindingsSpec>,
|
||||
/// Yank the entire line (`Y`).
|
||||
pub yank_line: Option<KeybindingsSpec>,
|
||||
/// Paste after cursor (`p`).
|
||||
pub paste_after: Option<KeybindingsSpec>,
|
||||
/// Begin delete operator; next key selects motion (`d`).
|
||||
pub start_delete_operator: Option<KeybindingsSpec>,
|
||||
/// Begin yank operator; next key selects motion (`y`).
|
||||
pub start_yank_operator: Option<KeybindingsSpec>,
|
||||
/// Cancel a pending operator and return to normal mode.
|
||||
pub cancel_operator: Option<KeybindingsSpec>,
|
||||
}
|
||||
|
||||
/// 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.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
|
||||
#[schemars(deny_unknown_fields)]
|
||||
pub struct TuiVimOperatorKeymap {
|
||||
/// Repeat delete operator to delete the whole line (`dd`).
|
||||
pub delete_line: Option<KeybindingsSpec>,
|
||||
/// Repeat yank operator to yank the whole line (`yy`).
|
||||
pub yank_line: Option<KeybindingsSpec>,
|
||||
/// Motion: left (`h`).
|
||||
pub motion_left: Option<KeybindingsSpec>,
|
||||
/// Motion: right (`l`).
|
||||
pub motion_right: Option<KeybindingsSpec>,
|
||||
/// Motion: up one line (`k`).
|
||||
pub motion_up: Option<KeybindingsSpec>,
|
||||
/// Motion: down one line (`j`).
|
||||
pub motion_down: Option<KeybindingsSpec>,
|
||||
/// Motion: to start of next word (`w`).
|
||||
pub motion_word_forward: Option<KeybindingsSpec>,
|
||||
/// Motion: to start of previous word (`b`).
|
||||
pub motion_word_backward: Option<KeybindingsSpec>,
|
||||
/// Motion: to end of current/next word (`e`).
|
||||
pub motion_word_end: Option<KeybindingsSpec>,
|
||||
/// Motion: to start of line (`0`).
|
||||
pub motion_line_start: Option<KeybindingsSpec>,
|
||||
/// Motion: to end of line (`$`).
|
||||
pub motion_line_end: Option<KeybindingsSpec>,
|
||||
/// Cancel the pending operator and return to normal mode.
|
||||
pub cancel: Option<KeybindingsSpec>,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
//! Key binding primitives and input matching for the TUI.
|
||||
//!
|
||||
//! This module provides `KeyBinding`, the runtime representation of a single
|
||||
//! keybinding (key code + modifier set), along with matching logic that handles
|
||||
//! cross-terminal inconsistencies in how shifted letters are reported.
|
||||
//!
|
||||
//! It also supplies rendering helpers that convert bindings into styled
|
||||
//! `ratatui::text::Span` values for UI hint display.
|
||||
|
||||
use crossterm::event::KeyCode;
|
||||
use crossterm::event::KeyEvent;
|
||||
use crossterm::event::KeyEventKind;
|
||||
@@ -15,6 +24,12 @@ const ALT_PREFIX: &str = "alt + ";
|
||||
const CTRL_PREFIX: &str = "ctrl + ";
|
||||
const SHIFT_PREFIX: &str = "shift + ";
|
||||
|
||||
/// A single key chord (key code + modifier set) used for input matching.
|
||||
///
|
||||
/// Matching via `is_press` handles both exact equality and a shifted-letter
|
||||
/// compatibility fallback for terminals that report uppercase letters without
|
||||
/// the SHIFT modifier flag. This means a binding defined as `shift-a` will
|
||||
/// match a terminal event of either `Shift+a` or plain `A`.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub(crate) struct KeyBinding {
|
||||
key: KeyCode,
|
||||
@@ -38,6 +53,17 @@ impl KeyBinding {
|
||||
self.matches_shifted_ascii_letter_compat(event)
|
||||
}
|
||||
|
||||
/// Cross-terminal compatibility for shifted ASCII letter bindings.
|
||||
///
|
||||
/// Some terminals (e.g. certain Kitty/Alacritty builds) report `Shift+a` as
|
||||
/// `KeyCode::Char('A')` with `KeyModifiers::NONE`, while others keep the
|
||||
/// lowercase char and set the SHIFT flag. This method accepts both forms
|
||||
/// when the binding specifies SHIFT + an ASCII lowercase letter, and
|
||||
/// preserves any additional modifiers (e.g. Ctrl) so `ctrl-shift-i`
|
||||
/// also matches `Ctrl+I`.
|
||||
///
|
||||
/// Only applies to ASCII lowercase letters — non-ASCII shift behavior
|
||||
/// (accented characters on European layouts, for instance) is not handled.
|
||||
fn matches_shifted_ascii_letter_compat(&self, event: KeyEvent) -> bool {
|
||||
let (KeyCode::Char(bound), KeyCode::Char(observed)) = (self.key, event.code) else {
|
||||
return false;
|
||||
|
||||
@@ -58,6 +58,12 @@ pub(crate) struct AppKeymap {
|
||||
pub(crate) toggle_vim_mode: Vec<KeyBinding>,
|
||||
}
|
||||
|
||||
/// Chat-level keybindings evaluated at the app event layer.
|
||||
///
|
||||
/// These participate in the first app-scope conflict validation pass alongside
|
||||
/// `AppKeymap` actions because both are checked before input reaches the
|
||||
/// composer. Dispatch gating (empty-composer guard for backtrack) happens in
|
||||
/// handler code, not here.
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct ChatKeymap {
|
||||
/// Start/advance edit-previous flow when composer is empty.
|
||||
@@ -66,6 +72,11 @@ pub(crate) struct ChatKeymap {
|
||||
pub(crate) confirm_edit_previous_message: Vec<KeyBinding>,
|
||||
}
|
||||
|
||||
/// Composer-level keybindings validated in the second app-scope conflict pass.
|
||||
///
|
||||
/// App-level handlers execute before the composer receives input, so any key
|
||||
/// bound here that also appears in `AppKeymap` would be silently intercepted.
|
||||
/// The conflict validator prevents this by checking app + composer uniqueness.
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct ComposerKeymap {
|
||||
/// Submit current draft.
|
||||
@@ -100,6 +111,14 @@ pub(crate) struct EditorKeymap {
|
||||
pub(crate) yank: Vec<KeyBinding>,
|
||||
}
|
||||
|
||||
/// Vim normal-mode keybindings for modal editing in the composer textarea.
|
||||
///
|
||||
/// 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
|
||||
/// 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.
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct VimNormalKeymap {
|
||||
pub(crate) enter_insert: Vec<KeyBinding>,
|
||||
@@ -126,6 +145,12 @@ pub(crate) struct VimNormalKeymap {
|
||||
pub(crate) cancel_operator: Vec<KeyBinding>,
|
||||
}
|
||||
|
||||
/// Vim operator-pending keybindings active after `d` or `y` 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
|
||||
/// motion range. Repeating the operator key (`dd`, `yy`) acts on the whole
|
||||
/// line. `Esc` cancels the pending operator and returns to normal mode.
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct VimOperatorKeymap {
|
||||
pub(crate) delete_line: Vec<KeyBinding>,
|
||||
|
||||
Reference in New Issue
Block a user