Files
claude-code/packages/@ant/ink/src/hooks/useExitOnCtrlCD.ts
2026-04-07 16:17:48 +08:00

96 lines
2.4 KiB
TypeScript

/**
* Minimal stub of useExitOnCtrlCD + useExitOnCtrlCDWithKeybindings.
*
* The original hooks depend on the keybinding system and useApp() exit.
* This stub provides the same interface with simplified Ctrl+C/D handling
* via useInput, suitable for the standalone @anthropic/ink package.
*/
import { useCallback, useState } from 'react'
import useInput from './use-input.js'
export type ExitState = {
pending: boolean
keyName: 'Ctrl-C' | 'Ctrl-D' | null
}
/**
* Minimal double-press exit handler.
* First Ctrl+C/D shows pending state, second press within timeout fires onExit.
*/
const DOUBLE_PRESS_TIMEOUT_MS = 800
function useDoublePress(
setPending: (pending: boolean) => void,
onDoublePress: () => void,
): () => void {
let lastPress = 0
let timeout: ReturnType<typeof setTimeout> | undefined
return () => {
const now = Date.now()
const timeSince = now - lastPress
const isDouble =
timeSince <= DOUBLE_PRESS_TIMEOUT_MS && timeout !== undefined
if (isDouble) {
clearTimeout(timeout)
timeout = undefined
setPending(false)
onDoublePress()
} else {
setPending(true)
clearTimeout(timeout)
timeout = setTimeout(() => {
setPending(false)
timeout = undefined
}, DOUBLE_PRESS_TIMEOUT_MS)
}
lastPress = now
}
}
/**
* Stub that provides ExitState for Ctrl+C/D double-press UI.
* In the standalone package, this uses useInput directly rather than the
* keybinding system.
*/
export function useExitOnCtrlCDWithKeybindings(
_onExit?: () => void,
_onInterrupt?: () => boolean,
isActive: boolean = true,
): ExitState {
const [exitState, setExitState] = useState<ExitState>({
pending: false,
keyName: null,
})
const handleCtrlC = useDoublePress(
(pending: boolean) =>
setExitState({ pending, keyName: pending ? 'Ctrl-C' : null }),
() => process.exit(0),
)
const handleCtrlD = useDoublePress(
(pending: boolean) =>
setExitState({ pending, keyName: pending ? 'Ctrl-D' : null }),
() => process.exit(0),
)
const handleInput = useCallback(
(_input: string, key: { ctrl?: boolean; name?: string }) => {
if (!isActive) return
if (key.ctrl && key.name === 'c') {
handleCtrlC()
} else if (key.ctrl && key.name === 'd') {
handleCtrlD()
}
},
[isActive, handleCtrlC, handleCtrlD],
)
useInput(handleInput, { isActive })
return exitState
}