summaryrefslogtreecommitdiff
path: root/packages/cli/src/ui/hooks/useKeypress.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/cli/src/ui/hooks/useKeypress.ts')
-rw-r--r--packages/cli/src/ui/hooks/useKeypress.ts88
1 files changed, 84 insertions, 4 deletions
diff --git a/packages/cli/src/ui/hooks/useKeypress.ts b/packages/cli/src/ui/hooks/useKeypress.ts
index a8adba8d..d3e3df5c 100644
--- a/packages/cli/src/ui/hooks/useKeypress.ts
+++ b/packages/cli/src/ui/hooks/useKeypress.ts
@@ -7,6 +7,7 @@
import { useEffect, useRef } from 'react';
import { useStdin } from 'ink';
import readline from 'readline';
+import { PassThrough } from 'stream';
export interface Key {
name: string;
@@ -48,7 +49,19 @@ export function useKeypress(
setRawMode(true);
- const rl = readline.createInterface({ input: stdin });
+ const keypressStream = new PassThrough();
+ let usePassthrough = false;
+ const nodeMajorVersion = parseInt(process.versions.node.split('.')[0], 10);
+ if (
+ nodeMajorVersion < 20 ||
+ process.env['PASTE_WORKAROUND'] === '1' ||
+ process.env['PASTE_WORKAROUND'] === 'true'
+ ) {
+ // Prior to node 20, node's built-in readline does not support bracketed
+ // paste mode. We hack by detecting it with our own handler.
+ usePassthrough = true;
+ }
+
let isPaste = false;
let pasteBuffer = Buffer.alloc(0);
@@ -79,11 +92,78 @@ export function useKeypress(
}
};
- readline.emitKeypressEvents(stdin, rl);
- stdin.on('keypress', handleKeypress);
+ const handleRawKeypress = (data: Buffer) => {
+ const PASTE_MODE_PREFIX = Buffer.from('\x1B[200~');
+ const PASTE_MODE_SUFFIX = Buffer.from('\x1B[201~');
+
+ let pos = 0;
+ while (pos < data.length) {
+ const prefixPos = data.indexOf(PASTE_MODE_PREFIX, pos);
+ const suffixPos = data.indexOf(PASTE_MODE_SUFFIX, pos);
+
+ // Determine which marker comes first, if any.
+ const isPrefixNext =
+ prefixPos !== -1 && (suffixPos === -1 || prefixPos < suffixPos);
+ const isSuffixNext =
+ suffixPos !== -1 && (prefixPos === -1 || suffixPos < prefixPos);
+
+ let nextMarkerPos = -1;
+ let markerLength = 0;
+
+ if (isPrefixNext) {
+ nextMarkerPos = prefixPos;
+ } else if (isSuffixNext) {
+ nextMarkerPos = suffixPos;
+ }
+ markerLength = PASTE_MODE_SUFFIX.length;
+
+ if (nextMarkerPos === -1) {
+ keypressStream.write(data.slice(pos));
+ return;
+ }
+
+ const nextData = data.slice(pos, nextMarkerPos);
+ if (nextData.length > 0) {
+ keypressStream.write(nextData);
+ }
+ const createPasteKeyEvent = (
+ name: 'paste-start' | 'paste-end',
+ ): Key => ({
+ name,
+ ctrl: false,
+ meta: false,
+ shift: false,
+ paste: false,
+ sequence: '',
+ });
+ if (isPrefixNext) {
+ handleKeypress(undefined, createPasteKeyEvent('paste-start'));
+ } else if (isSuffixNext) {
+ handleKeypress(undefined, createPasteKeyEvent('paste-end'));
+ }
+ pos = nextMarkerPos + markerLength;
+ }
+ };
+
+ let rl: readline.Interface;
+ if (usePassthrough) {
+ rl = readline.createInterface({ input: keypressStream });
+ readline.emitKeypressEvents(keypressStream, rl);
+ keypressStream.on('keypress', handleKeypress);
+ stdin.on('data', handleRawKeypress);
+ } else {
+ rl = readline.createInterface({ input: stdin });
+ readline.emitKeypressEvents(stdin, rl);
+ stdin.on('keypress', handleKeypress);
+ }
return () => {
- stdin.removeListener('keypress', handleKeypress);
+ if (usePassthrough) {
+ keypressStream.removeListener('keypress', handleKeypress);
+ stdin.removeListener('data', handleRawKeypress);
+ } else {
+ stdin.removeListener('keypress', handleKeypress);
+ }
rl.close();
setRawMode(false);