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.

14
Files
2
Default Modes
40+
Bindings
TS
Language

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

  1. Key Event — Browser/terminal emits a keydown event with ctrlKey: true, key: "k".
  2. React Capture — The KeybindingProvider context wrapper captures the raw key event.
  3. Normalization — parse.ts normalizes the event into a canonical string: "ctrl+k".
  4. Mode Selection — The resolver checks the active mode (Vim, Emacs, or Custom).
  5. User Override Check — User config is checked first; if "ctrl+k" has a custom binding, it wins.
  6. Default Lookup — If no user override, the default preset for the active mode is consulted.
  7. Command Resolution — The matched binding returns a command name, e.g., "clearScreen".
  8. Hook Notification — useKeybinding("clearScreen") fires its callback in the subscribed component.
  9. Action Execution — The component handler clears the screen and re-renders the REPL.

Key Classes & Functions

NameFileDescription
defaultBindingsdefaultBindings.tsExports Vim and Emacs default binding maps (key combo → command name)
KeybindingProviderKeybindingProviderSetup.tsxReact context provider that wraps the app and captures key events
parseKeyComboparse.tsNormalizes raw key events into canonical combo strings like "ctrl+shift+p"
resolveBindingresolver.tsMatches a normalized key combo against all binding layers, returns command
validateBindingsschema.tsValidates user binding config against JSON schema, returns validation errors
useKeybindinghook.tsReact hook: subscribes a component to fire a callback when a command triggers
KeybindingContextcontext.tsReact context object holding current bindings and the active mode
loadUserBindingsloader.tsReads user keybinding config from .claude/keybindings.json
mergeBindingsmerge.tsMerges user overrides on top of default presets, user wins on conflict
KeyCombotypes.tsTypeScript type for a normalized key combination string
BindingMaptypes.tsType for the full binding map: Record<KeyCombo, CommandName>
CommandNametypes.tsUnion type of all valid command name strings

Configuration

SettingDefaultDescription
mode"vim"Active keybinding mode: "vim", "emacs", or "custom"
configPath.claude/keybindings.jsonPath to user keybinding override file
enableRepeattrueWhether held keys repeat their bound command
repeatDelay500Initial delay in ms before key repeat starts
repeatRate50Interval in ms between repeated key events
strictModefalseWhen true, unrecognized key combos are silently ignored
allowChordstrueWhether multi-key chords (e.g., Ctrl+K Ctrl+C) are supported
chordTimeout1000Max ms between chord keystrokes before chord resets

Default Bindings (Vim Mode)

Key ComboCommandDescription
Ctrl+CcancelCancel current operation or clear input
Ctrl+DexitExit the REPL session
Ctrl+LclearScreenClear the terminal screen
Ctrl+KkillLineDelete from cursor to end of line
Ctrl+UclearLineDelete entire input line
Ctrl+RsearchHistorySearch backwards through command history
EscapenormalModeSwitch to Vim normal mode from insert
TabautocompleteTrigger 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>;
}