diff --git a/src/hooks/use-input.ts b/src/hooks/use-input.ts index 3a92b45b3..93d782313 100644 --- a/src/hooks/use-input.ts +++ b/src/hooks/use-input.ts @@ -137,6 +137,14 @@ const useInput = (inputHandler: Handler, options: Options = {}) => { const handleData = (data: Buffer) => { let input = String(data); + const meta = + input.startsWith('\u001B\u001B') || // Meta + arrow keys + (input.startsWith('\u001B') && !input.startsWith('\u001B[')); // Meta + character + + if (meta && input.length > 1) { + input = input.slice(1); + } + const key = { upArrow: input === '\u001B[A', downArrow: input === '\u001B[B', @@ -147,11 +155,12 @@ const useInput = (inputHandler: Handler, options: Options = {}) => { return: input === '\r', escape: input === '\u001B', ctrl: false, - shift: false, + // Shift + Tab + shift: input === '\u001B[Z', tab: input === '\t' || input === '\u001B[Z', backspace: input === '\u0008', delete: input === '\u007F' || input === '\u001B[3~', - meta: false + meta }; // Copied from `keypress` module @@ -161,30 +170,14 @@ const useInput = (inputHandler: Handler, options: Options = {}) => { // eslint-disable-next-line unicorn/prefer-code-point input.charCodeAt(0) + 'a'.charCodeAt(0) - 1 ); - key.ctrl = true; - } - if ( - input.startsWith('\u001B') && - !key.upArrow && - !key.downArrow && - !key.leftArrow && - !key.rightArrow && - !key.pageUp && - !key.pageDown - ) { - input = input.slice(1); - key.meta = true; + key.ctrl = true; } const isLatinUppercase = input >= 'A' && input <= 'Z'; const isCyrillicUppercase = input >= 'А' && input <= 'Я'; - if (input.length === 1 && (isLatinUppercase || isCyrillicUppercase)) { - key.shift = true; - } - // Shift+Tab - if (key.tab && input === '[Z') { + if (input.length === 1 && (isLatinUppercase || isCyrillicUppercase)) { key.shift = true; } diff --git a/test/fixtures/use-input.tsx b/test/fixtures/use-input.tsx index 6b39df1ce..11eec8ee2 100644 --- a/test/fixtures/use-input.tsx +++ b/test/fixtures/use-input.tsx @@ -51,6 +51,26 @@ function UserInput({test}: {test: string | undefined}) { return; } + if (test === 'upArrowMeta' && key.upArrow && key.meta) { + exit(); + return; + } + + if (test === 'downArrowMeta' && key.downArrow && key.meta) { + exit(); + return; + } + + if (test === 'leftArrowMeta' && key.leftArrow && key.meta) { + exit(); + return; + } + + if (test === 'rightArrowMeta' && key.rightArrow && key.meta) { + exit(); + return; + } + if (test === 'pageDown' && key.pageDown && !key.meta) { exit(); return; diff --git a/test/hooks.tsx b/test/hooks.tsx index 05cf96eac..6de614006 100644 --- a/test/hooks.tsx +++ b/test/hooks.tsx @@ -135,6 +135,34 @@ test.serial('useInput - handle right arrow', async t => { t.true(ps.output.includes('exited')); }); +test.serial('useInput - handle meta + up arrow', async t => { + const ps = term('use-input', ['upArrowMeta']); + ps.write('\u001B\u001B[A'); + await ps.waitForExit(); + t.true(ps.output.includes('exited')); +}); + +test.serial('useInput - handle meta + down arrow', async t => { + const ps = term('use-input', ['downArrowMeta']); + ps.write('\u001B\u001B[B'); + await ps.waitForExit(); + t.true(ps.output.includes('exited')); +}); + +test.serial('useInput - handle meta + left arrow', async t => { + const ps = term('use-input', ['leftArrowMeta']); + ps.write('\u001B\u001B[D'); + await ps.waitForExit(); + t.true(ps.output.includes('exited')); +}); + +test.serial('useInput - handle meta + right arrow', async t => { + const ps = term('use-input', ['rightArrowMeta']); + ps.write('\u001B\u001B[C'); + await ps.waitForExit(); + t.true(ps.output.includes('exited')); +}); + test.serial('useInput - handle page down', async t => { const ps = term('use-input', ['pageDown']); ps.write('\u001B[6~');