MCPcopy
hub / github.com/codeaashu/claude-code / handleKeyDown

Function handleKeyDown

src/hooks/useVoiceIntegration.tsx:468–647  ·  view source on GitHub ↗
(e: KeyboardEvent)

Source from the content-addressed store, hash-verified

466 }
467 }, [voiceState, setVoiceState]);
468 const handleKeyDown = (e: KeyboardEvent): void => {
469 if (!voiceEnabled) return;
470
471 // PromptInput is not a valid transcript target — let the hold key
472 // flow through instead of swallowing it into stale refs (#33556).
473 // Two distinct unmount/unfocus paths (both needed):
474 // - !isActive: local-jsx command hid PromptInput (shouldHidePromptInput)
475 // without registering an overlay — e.g. /install-github-app,
476 // /plugin. Mirrors CommandKeybindingHandlers' isActive gate.
477 // - isModalOverlayActive: overlay (permission dialog, Select with
478 // onCancel) has focus; PromptInput is mounted but focus=false.
479 if (!isActive || isModalOverlayActive) return;
480
481 // null means the user overrode the default (null-unbind/reassign) —
482 // hold-to-talk is disabled via binding. To toggle the feature
483 // itself, use /voice.
484 if (voiceKeystroke === null) return;
485
486 // Match the configured key. Bare chars match by content (handles
487 // batched auto-repeat like "vvv") with a modifier reject so e.g.
488 // ctrl+v doesn't trip a "v" binding. Modifier combos go through
489 // matchesKeyboardEvent (one event per repeat, no batching).
490 let repeatCount: number;
491 if (bareChar !== null) {
492 if (e.ctrl || e.meta || e.shift) return;
493 // When bound to space, also accept U+3000 (full-width space) —
494 // CJK IMEs emit it for the same physical key.
495 const normalized = bareChar === ' ' ? normalizeFullWidthSpace(e.key) : e.key;
496 // Fast-path: normal typing (any char that isn't the bound one)
497 // bails here without allocating. The repeat() check only matters
498 // for batched auto-repeat (input.length > 1) which is rare.
499 if (normalized[0] !== bareChar) return;
500 if (normalized.length > 1 && normalized !== bareChar.repeat(normalized.length)) return;
501 repeatCount = normalized.length;
502 } else {
503 if (!matchesKeyboardEvent(e, voiceKeystroke)) return;
504 repeatCount = 1;
505 }
506
507 // Guard: only swallow keypresses when recording was triggered by
508 // key-hold. Focus-mode recording also sets voiceState to 'recording',
509 // but keypresses should flow through normally (voiceHandleKeyEvent
510 // returns early for focus-triggered sessions). We also check voiceState
511 // from the store so that if voiceHandleKeyEvent() fails to transition
512 // state (module not loaded, stream unavailable) we don't permanently
513 // swallow keypresses.
514 const currentVoiceState = getVoiceState().voiceState;
515 if (isHoldActiveRef.current && currentVoiceState !== 'idle') {
516 // Already recording — swallow continued keypresses and forward
517 // to voice for release detection. For bare chars, defensively
518 // strip in case the text input handler fired before this one
519 // (listener order is not guaranteed). Modifier combos don't
520 // insert text, so nothing to strip.
521 e.stopImmediatePropagation();
522 if (bareChar !== null) {
523 stripTrailing(repeatCount, {
524 char: bareChar,
525 floor: recordingFloorRef.current

Callers 1

Calls 3

normalizeFullWidthSpaceFunction · 0.85
matchesKeyboardEventFunction · 0.85

Tested by

no test coverage detected