AI Assistant

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 const

Motions

Motions are pure functions that resolve to cursor positions without side effects:

MotionDescription
h, lLeft, right
j, kDown, up (logical lines)
gj, gkDown, up (display lines)
w, b, eNext word, previous word, end of word
W, B, ENext WORD, previous WORD, end of WORD
0, ^, $Start of line, first non-blank, end of line
GStart 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:

TypeDescription
w, WWord / WORD
", ', `Quoted strings
(, ), bParentheses
[, ]Brackets
{, }, BBraces
<, >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:

1

Build flag

The VOICE_MODE feature flag must be enabled at compile time. External builds have this disabled.

2

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.

3

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(): boolean

When 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:

ContextDescription
GlobalActive everywhere, regardless of focus
ChatWhen the chat input is focused
AutocompleteWhen autocomplete menu is visible
ConfirmationWhen a confirmation/permission dialog is shown
HelpWhen the help overlay is open
TranscriptWhen viewing the transcript
HistorySearchWhen searching command history (ctrl+r)
TaskWhen a task/agent is running in the foreground
ThemePickerWhen the theme picker is open
SettingsWhen the settings menu is open
TabsWhen tab navigation is active
AttachmentsWhen navigating image attachments
FooterWhen footer indicators are focused
MessageSelectorWhen the message selector (rewind) is open
DiffDialogWhen the diff dialog is open
ModelPickerWhen the model picker is open
SelectWhen a select/list component is focused
PluginWhen the plugin dialog is open

Default Bindings

Key default bindings by context:

KeyAction
ctrl+capp:interrupt
ctrl+dapp:exit
ctrl+lapp:redraw
ctrl+tapp:toggleTodos
ctrl+oapp:toggleTranscript
ctrl+rhistory: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+v on Windows (where ctrl+v is system paste), ctrl+v elsewhere
  • Mode cycle: meta+m on Windows without VT mode support, shift+tab elsewhere
  • Terminal VT mode: Detected based on Bun/Node version for reliable modifier key handling