Something went wrong. Try again.
source dump of claude code forked from oppi.li/claude-code
Something went wrong. Try again.
3.2 kB · 95 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596import { useCallback, useMemo, useState } from 'react'import useApp from '../ink/hooks/use-app.js'import type { KeybindingContextName } from '../keybindings/types.js'import { useDoublePress } from './useDoublePress.js'
export type ExitState = { pending: boolean keyName: 'Ctrl-C' | 'Ctrl-D' | null}
type KeybindingOptions = { context?: KeybindingContextName isActive?: boolean}
type UseKeybindingsHook = ( handlers: Record<string, () => void>, options?: KeybindingOptions,) => void
/** * Handle ctrl+c and ctrl+d for exiting the application. * * Uses a time-based double-press mechanism: * - First press: Shows "Press X again to exit" message * - Second press within timeout: Exits the application * * Note: We use time-based double-press rather than the chord system because * we want the first ctrl+c to also trigger interrupt (handled elsewhere). * The chord system would prevent the first press from firing any action. * * These keys are hardcoded and cannot be rebound via keybindings.json. * * @param useKeybindingsHook - The useKeybindings hook to use for registering handlers * (dependency injection to avoid import cycles) * @param onInterrupt - Optional callback for features to handle interrupt (ctrl+c). * Return true if handled, false to fall through to double-press exit. * @param onExit - Optional custom exit handler * @param isActive - Whether the keybinding is active (default true). Set false * while an embedded TextInput is focused — TextInput's own * ctrl+c/d handlers will manage cancel/exit, and Dialog's * handler would otherwise double-fire (child useInput runs * before parent useKeybindings, so both see every keypress). */export function useExitOnCtrlCD( useKeybindingsHook: UseKeybindingsHook, onInterrupt?: () => boolean, onExit?: () => void, isActive = true,): ExitState { const { exit } = useApp() const [exitState, setExitState] = useState<ExitState>({ pending: false, keyName: null, })
const exitFn = useMemo(() => onExit ?? exit, [onExit, exit])
// Double-press handler for ctrl+c const handleCtrlCDoublePress = useDoublePress( pending => setExitState({ pending, keyName: 'Ctrl-C' }), exitFn, )
// Double-press handler for ctrl+d const handleCtrlDDoublePress = useDoublePress( pending => setExitState({ pending, keyName: 'Ctrl-D' }), exitFn, )
// Handler for app:interrupt (ctrl+c by default) // Let features handle interrupt first via callback const handleInterrupt = useCallback(() => { if (onInterrupt?.()) return // Feature handled it handleCtrlCDoublePress() }, [handleCtrlCDoublePress, onInterrupt])
// Handler for app:exit (ctrl+d by default) // This also uses double-press to confirm exit const handleExit = useCallback(() => { handleCtrlDDoublePress() }, [handleCtrlDDoublePress])
const handlers = useMemo( () => ({ 'app:interrupt': handleInterrupt, 'app:exit': handleExit, }), [handleInterrupt, handleExit], )
useKeybindingsHook(handlers, { context: 'Global', isActive })
return exitState}