keybindings — Keyboard Shortcut Configuration
Keyboard Shortcut Configuration
Full keyboard shortcut system with Vim and Emacs mode defaults, user-customizable bindings, a React context provider, key event parser, resolver that matches keypresses to commands, schema validation, and a useKeybinding React hook for component integration.
🔵 Vim Mode 🟢 Emacs Mode 🟡 Custom Bindings 🔴 React Hook
💡 What is this?
The keybindings module provides the complete keyboard shortcut infrastructure for Claude Code's terminal UI. It supports Vim and Emacs presets out of the box, lets users override any binding, and exposes a React hook so components can respond to key events.
🧩 Analogy: Think of it like VS Code's keybinding system
Just as VS Code has a keybindings.json where users override defaults, and a resolver that maps key chords to commands, this module provides the same layered approach: default presets (Vim/Emacs), user overrides (config file), a parser that normalizes key events, and a resolver that produces the final command to execute.
Architecture
flowchart LR
subgraph Defaults["Default Presets"]
VIM["Vim Bindings
defaultBindings.ts"]
EMACS["Emacs Bindings
defaultBindings.ts"]
end
subgraph Config["User Config"]
PARSE["Parser
parse.ts"]
SCHEMA["Schema Validator
schema.ts"]
end
subgraph Runtime["Runtime Resolution"]
CTX["KeybindingProvider
Setup.tsx"]
RES["Resolver
resolver.ts"]
HOOK["useKeybinding
hook.ts"]
end
VIM --> CTX
EMACS --> CTX
PARSE --> SCHEMA
SCHEMA --> CTX
CTX --> RES
RES --> HOOK
HOOK --> |Command| UI["React Components"]
Data Flow
flowchart TD A["User presses key combo
e.g., Ctrl+K"] --> B["KeyEvent captured
by React"] B --> C["Parser normalizes event
parse.ts"] C --> D["Resolver searches bindings
resolver.ts"] D --> E{"Match found?"} E -->|Yes| F["Execute command
e.g., clearScreen"] E -->|No| G["Pass to default
input handler"] F --> H["Update UI state
via useKeybinding"]
🚶 Step-by-Step Narrated Walkthrough
Follow a single Ctrl+K keypress through the keybinding pipeline:
- Key Event — Browser/terminal emits a
keydownevent withctrlKey: true, key: "k". - React Capture — The
KeybindingProvidercontext wrapper captures the raw key event. - Normalization —
parse.tsnormalizes the event into a canonical string:"ctrl+k". - Mode Selection — The resolver checks the active mode (Vim, Emacs, or Custom).
- User Override Check — User config is checked first; if
"ctrl+k"has a custom binding, it wins. - Default Lookup — If no user override, the default preset for the active mode is consulted.
- Command Resolution — The matched binding returns a command name, e.g.,
"clearScreen". - Hook Notification —
useKeybinding("clearScreen")fires its callback in the subscribed component. - Action Execution — The component handler clears the screen and re-renders the REPL.
Key Classes & Functions
| Name | File | Description |
|---|---|---|
defaultBindings | defaultBindings.ts | Exports Vim and Emacs default binding maps (key combo → command name) |
KeybindingProvider | KeybindingProviderSetup.tsx | React context provider that wraps the app and captures key events |
parseKeyCombo | parse.ts | Normalizes raw key events into canonical combo strings like "ctrl+shift+p" |
resolveBinding | resolver.ts | Matches a normalized key combo against all binding layers, returns command |
validateBindings | schema.ts | Validates user binding config against JSON schema, returns validation errors |
useKeybinding | hook.ts | React hook: subscribes a component to fire a callback when a command triggers |
KeybindingContext | context.ts | React context object holding current bindings and the active mode |
loadUserBindings | loader.ts | Reads user keybinding config from .claude/keybindings.json |
mergeBindings | merge.ts | Merges user overrides on top of default presets, user wins on conflict |
KeyCombo | types.ts | TypeScript type for a normalized key combination string |
BindingMap | types.ts | Type for the full binding map: Record<KeyCombo, CommandName> |
CommandName | types.ts | Union type of all valid command name strings |
Configuration
| Setting | Default | Description |
|---|---|---|
mode | "vim" | Active keybinding mode: "vim", "emacs", or "custom" |
configPath | .claude/keybindings.json | Path to user keybinding override file |
enableRepeat | true | Whether held keys repeat their bound command |
repeatDelay | 500 | Initial delay in ms before key repeat starts |
repeatRate | 50 | Interval in ms between repeated key events |
strictMode | false | When true, unrecognized key combos are silently ignored |
allowChords | true | Whether multi-key chords (e.g., Ctrl+K Ctrl+C) are supported |
chordTimeout | 1000 | Max ms between chord keystrokes before chord resets |
Default Bindings (Vim Mode)
| Key Combo | Command | Description |
|---|---|---|
Ctrl+C | cancel | Cancel current operation or clear input |
Ctrl+D | exit | Exit the REPL session |
Ctrl+L | clearScreen | Clear the terminal screen |
Ctrl+K | killLine | Delete from cursor to end of line |
Ctrl+U | clearLine | Delete entire input line |
Ctrl+R | searchHistory | Search backwards through command history |
Escape | normalMode | Switch to Vim normal mode from insert |
Tab | autocomplete | Trigger slash command autocompletion |
Directory Map
Source tree for keybindings
keybindings/
├── index.ts ← Public API barrel export
├── defaultBindings.ts ← Vim & Emacs default binding maps
├── KeybindingProviderSetup.tsx ← React context provider wrapper
├── context.ts ← React context definition
├── hook.ts ← useKeybinding React hook
├── parse.ts ← Key event normalizer/parser
├── resolver.ts ← Binding resolution engine
├── schema.ts ← JSON schema for user config
├── loader.ts ← User config file reader
├── merge.ts ← Binding merge logic
├── types.ts ← TypeScript type definitions
├── __tests__/
│ ├── parse.test.ts ← Parser normalization tests
│ ├── resolver.test.ts ← Resolution priority tests
│ └── defaultBindings.test.ts ← Default binding coverage
Code Example
import { useKeybinding } from "./keybindings/hook";
import { KeybindingProvider } from "./keybindings/KeybindingProviderSetup";
// Wrap app with provider
function App() {
return (
<KeybindingProvider mode="vim">
<ReplView />
</KeybindingProvider>
);
}
// In a component, subscribe to a command
function ReplView() {
useKeybinding("clearScreen", () => {
clearTerminal();
});
useKeybinding("submitInput", () => {
handleSubmit(currentInput);
});
return <div>{/* REPL UI */}</div>;
}