Files
claude-code/src/keybindings/KeybindingProviderSetup.tsx
2026-05-01 21:39:30 +08:00

92 lines
2.9 KiB
TypeScript

/**
* App-specific wrapper around ink's KeybindingSetup.
*
* Wires up app-specific dependencies (notification system, binding loading,
* file watching, debug logging) and re-exports as KeybindingSetup.
*/
import { useCallback } from 'react';
import { useNotifications } from '../context/notifications.js';
import { count } from '../utils/array.js';
import { logForDebugging } from '../utils/debug.js';
import { plural } from '../utils/stringUtils.js';
import { KeybindingSetup as InkKeybindingSetup } from '@anthropic/ink';
import type { KeybindingWarning } from '@anthropic/ink';
import {
initializeKeybindingWatcher,
loadKeybindingsSyncWithWarnings,
subscribeToKeybindingChanges,
} from './loadUserBindings.js';
type Props = {
children: React.ReactNode;
};
/**
* Keybinding provider with default + user bindings and hot-reload support.
*
* Usage: Wrap your app with this provider to enable keybinding support.
*
* ```tsx
* <AppStateProvider>
* <KeybindingSetup>
* <REPL ... />
* </KeybindingSetup>
* </AppStateProvider>
* ```
*
* Features:
* - Loads default bindings from code
* - Merges with user bindings from ~/.claude/keybindings.json
* - Watches for file changes and reloads automatically (hot-reload)
* - User bindings override defaults (later entries win)
* - Chord support with automatic timeout
*/
export function KeybindingSetup({ children }: Props): React.ReactNode {
const { addNotification, removeNotification } = useNotifications();
const handleWarnings = useCallback(
(warnings: KeybindingWarning[], _isReload: boolean) => {
const notificationKey = 'keybinding-config-warning';
if (warnings.length === 0) {
removeNotification(notificationKey);
return;
}
const errorCount = count(warnings, w => w.severity === 'error');
const warnCount = count(warnings, w => w.severity === 'warning');
let message: string;
if (errorCount > 0 && warnCount > 0) {
message = `Found ${errorCount} keybinding ${plural(errorCount, 'error')} and ${warnCount} ${plural(warnCount, 'warning')}`;
} else if (errorCount > 0) {
message = `Found ${errorCount} keybinding ${plural(errorCount, 'error')}`;
} else {
message = `Found ${warnCount} keybinding ${plural(warnCount, 'warning')}`;
}
message += ' · /doctor for details';
addNotification({
key: notificationKey,
text: message,
color: errorCount > 0 ? 'error' : 'warning',
priority: errorCount > 0 ? 'immediate' : 'high',
timeoutMs: 60000,
});
},
[addNotification, removeNotification],
);
return (
<InkKeybindingSetup
loadBindings={loadKeybindingsSyncWithWarnings}
subscribeToChanges={subscribeToKeybindingChanges}
initWatcher={initializeKeybindingWatcher}
onWarnings={handleWarnings}
onDebugLog={logForDebugging}
>
{children}
</InkKeybindingSetup>
);
}