1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
|
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { useEffect, useRef } from 'react';
import { useStdin } from 'ink';
import readline from 'readline';
export interface Key {
name: string;
ctrl: boolean;
meta: boolean;
shift: boolean;
paste: boolean;
sequence: string;
}
/**
* A hook that listens for keypress events from stdin, providing a
* key object that mirrors the one from Node's `readline` module,
* adding a 'paste' flag for characters input as part of a bracketed
* paste (when enabled).
*
* Pastes are currently sent as a single key event where the full paste
* is in the sequence field.
*
* @param onKeypress - The callback function to execute on each keypress.
* @param options - Options to control the hook's behavior.
* @param options.isActive - Whether the hook should be actively listening for input.
*/
export function useKeypress(
onKeypress: (key: Key) => void,
{ isActive }: { isActive: boolean },
) {
const { stdin, setRawMode } = useStdin();
const onKeypressRef = useRef(onKeypress);
useEffect(() => {
onKeypressRef.current = onKeypress;
}, [onKeypress]);
useEffect(() => {
if (!isActive || !stdin.isTTY) {
return;
}
setRawMode(true);
const rl = readline.createInterface({ input: stdin });
let isPaste = false;
let pasteBuffer = Buffer.alloc(0);
const handleKeypress = (_: unknown, key: Key) => {
if (key.name === 'paste-start') {
isPaste = true;
} else if (key.name === 'paste-end') {
isPaste = false;
onKeypressRef.current({
name: '',
ctrl: false,
meta: false,
shift: false,
paste: true,
sequence: pasteBuffer.toString(),
});
pasteBuffer = Buffer.alloc(0);
} else {
if (isPaste) {
pasteBuffer = Buffer.concat([pasteBuffer, Buffer.from(key.sequence)]);
} else {
// Handle special keys
if (key.name === 'return' && key.sequence === '\x1B\r') {
key.meta = true;
}
onKeypressRef.current({ ...key, paste: isPaste });
}
}
};
readline.emitKeypressEvents(stdin, rl);
stdin.on('keypress', handleKeypress);
return () => {
stdin.removeListener('keypress', handleKeypress);
rl.close();
setRawMode(false);
// If we are in the middle of a paste, send what we have.
if (isPaste) {
onKeypressRef.current({
name: '',
ctrl: false,
meta: false,
shift: false,
paste: true,
sequence: pasteBuffer.toString(),
});
pasteBuffer = Buffer.alloc(0);
}
};
}, [isActive, stdin, setRawMode]);
}
|