Input Modes
Claude Code supports multiple input modes: standard editing, vim-style modal editing, voice input, and fully customizable keybindings.
Vim Mode
Vim mode provides modal editing in the chat input, implementing a state machine with INSERT and NORMAL modes.
State Machine
The vim state machine is defined by a discriminated union:
type VimState =
| { mode: 'INSERT'; insertedText: string }
| { mode: 'NORMAL'; command: CommandState }In INSERT mode, text is tracked for dot-repeat. In NORMAL mode, a CommandState machine parses multi-key commands:
VimState
┌──────────────────────────────┬──────────────────────────────────────┐
│ INSERT │ NORMAL │
│ (tracks insertedText) │ (CommandState machine) │
│ │ │
│ │ idle ──┬─[d/c/y]──► operator │
│ │ ├─[1-9]────► count │
│ │ ├─[fFtT]───► find │
│ │ ├─[g]──────► g │
│ │ ├─[r]──────► replace │
│ │ └─[><]─────► indent │
│ │ │
│ │ operator ─┬─[motion]──► execute │
│ │ ├─[0-9]────► operatorCount│
│ │ ├─[ia]─────► textObject │
│ │ └─[fFtT]───► operatorFind │
└──────────────────────────────┴──────────────────────────────────────┘Command States
type CommandState =
| { type: 'idle' }
| { type: 'count'; digits: string }
| { type: 'operator'; op: Operator; count: number }
| { type: 'operatorCount'; op: Operator; count: number; digits: string }
| { type: 'operatorFind'; op: Operator; count: number; find: FindType }
| { type: 'operatorTextObj'; op: Operator; count: number; scope: TextObjScope }
| { type: 'find'; find: FindType; count: number }
| { type: 'g'; count: number }
| { type: 'operatorG'; op: Operator; count: number }
| { type: 'replace'; count: number }
| { type: 'indent'; dir: '>' | '<'; count: number }Operators
Three operators: d (delete), c (change), y (yank):
const OPERATORS = { d: 'delete', c: 'change', y: 'yank' } as constMotions
Motions are pure functions that resolve to cursor positions without side effects:
| Motion | Description |
|---|---|
h, l | Left, right |
j, k | Down, up (logical lines) |
gj, gk | Down, up (display lines) |
w, b, e | Next word, previous word, end of word |
W, B, E | Next WORD, previous WORD, end of WORD |
0, ^, $ | Start of line, first non-blank, end of line |
G | Start of last line |
Motions are classified as inclusive (e, E, $) or exclusive, and linewise (j, k) or characterwise.
Text Objects
Text objects combine a scope (i for inner, a for around) with a type:
| Type | Description |
|---|---|
w, W | Word / WORD |
", ', ` | Quoted strings |
(, ), b | Parentheses |
[, ] | Brackets |
{, }, B | Braces |
<, > | Angle brackets |
Persistent State
State that survives across commands for repeats and pastes:
type PersistentState = {
lastChange: RecordedChange | null // For dot-repeat
lastFind: { type: FindType; char: string } | null
register: string // Yank register
registerIsLinewise: boolean
}The count limit is capped at MAX_VIM_COUNT = 10000 to prevent accidental infinite operations.
Voice Input
Voice mode provides speech-to-text (STT) and text-to-speech (TTS) capabilities, gated behind the VOICE_MODE build flag.
Availability Checks
Voice mode requires three conditions:
Build flag
The VOICE_MODE feature flag must be enabled at compile time. External builds have this disabled.
Kill switch
The GrowthBook flag tengu_amber_quartz_disabled must not be set. A missing or stale cache reads as "not killed" so fresh installs work immediately.
Authentication
The user must have a valid Anthropic OAuth token. Voice uses the voice_stream endpoint on claude.ai, which is not available with API keys, Bedrock, Vertex, or Foundry.
Check Functions
// Full runtime check: auth + GrowthBook kill-switch
function isVoiceModeEnabled(): boolean
// Auth-only check (memoized via getClaudeAIOAuthTokens)
function hasVoiceAuth(): boolean
// Kill-switch check only
function isVoiceGrowthBookEnabled(): booleanWhen voice is active, the space key is bound to voice:pushToTalk in the Chat context for hold-to-talk activation.
Keybindings System
The keybinding system maps keystrokes to actions across different UI contexts. Default bindings are defined in code, and users can override them via ~/.claude/keybindings.json.
Contexts
Keybindings are scoped to UI contexts:
Available Contexts
| Context | Description |
|---|---|
Global | Active everywhere, regardless of focus |
Chat | When the chat input is focused |
Autocomplete | When autocomplete menu is visible |
Confirmation | When a confirmation/permission dialog is shown |
Help | When the help overlay is open |
Transcript | When viewing the transcript |
HistorySearch | When searching command history (ctrl+r) |
Task | When a task/agent is running in the foreground |
ThemePicker | When the theme picker is open |
Settings | When the settings menu is open |
Tabs | When tab navigation is active |
Attachments | When navigating image attachments |
Footer | When footer indicators are focused |
MessageSelector | When the message selector (rewind) is open |
DiffDialog | When the diff dialog is open |
ModelPicker | When the model picker is open |
Select | When a select/list component is focused |
Plugin | When the plugin dialog is open |
Default Bindings
Key default bindings by context:
| Key | Action |
|---|---|
ctrl+c | app:interrupt |
ctrl+d | app:exit |
ctrl+l | app:redraw |
ctrl+t | app:toggleTodos |
ctrl+o | app:toggleTranscript |
ctrl+r | history:search |
User Overrides
Users customize keybindings via ~/.claude/keybindings.json:
{
"$schema": "...",
"bindings": [
{
"context": "Chat",
"bindings": {
"ctrl+enter": "chat:submit",
"enter": "chat:newline",
"ctrl+k": null
}
}
]
}Set a binding to null to unbind a default shortcut. Bindings can also reference slash commands using the command: prefix (e.g., "command:help", "command:compact").
Schema Validation
The KeybindingsSchema validates the configuration:
const KeybindingsSchema = z.object({
$schema: z.string().optional(),
$docs: z.string().optional(),
bindings: z.array(KeybindingBlockSchema()),
})Each KeybindingBlock maps a context to a record of keystroke patterns to actions:
const KeybindingBlockSchema = z.object({
context: z.enum(KEYBINDING_CONTEXTS),
bindings: z.record(
z.string(), // Keystroke pattern
z.union([
z.enum(KEYBINDING_ACTIONS),
z.string().regex(/^command:[a-zA-Z0-9:\-_]+$/),
z.null(),
]),
),
})Reserved Shortcuts
ctrl+c and ctrl+d use special time-based double-press handling and cannot be rebound by users. Attempting to override these triggers a validation error from reservedShortcuts.ts.
Chord Support
Keybindings support chord sequences (e.g., ctrl+x ctrl+k for chat:killAgents, ctrl+x ctrl+e for chat:externalEditor). The resolver tracks prefix state so the first key in a chord does not trigger a standalone action.
Platform Adaptations
The system adapts to platform differences:
- Image paste:
alt+von Windows (wherectrl+vis system paste),ctrl+velsewhere - Mode cycle:
meta+mon Windows without VT mode support,shift+tabelsewhere - Terminal VT mode: Detected based on Bun/Node version for reliable modifier key handling