init claude-code
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
import { 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
|
||||
}
|
||||
Reference in New Issue
Block a user