diff --git a/packages/cli/src/ui/contexts/KeypressContext.test.tsx b/packages/cli/src/ui/contexts/KeypressContext.test.tsx index 6fb8a6f2ab..3954e4aee3 100644 --- a/packages/cli/src/ui/contexts/KeypressContext.test.tsx +++ b/packages/cli/src/ui/contexts/KeypressContext.test.tsx @@ -405,12 +405,21 @@ describe('KeypressContext', () => { describe('Parameterized functional keys', () => { it.each([ - // Parameterized + // ModifyOtherKeys + { sequence: `\x1b[27;2;13~`, expected: { name: 'return', shift: true } }, + { sequence: `\x1b[27;5;13~`, expected: { name: 'return', ctrl: true } }, + { sequence: `\x1b[27;5;9~`, expected: { name: 'tab', ctrl: true } }, + { + sequence: `\x1b[27;6;9~`, + expected: { name: 'tab', ctrl: true, shift: true }, + }, + // XTerm Function Key { sequence: `\x1b[1;129A`, expected: { name: 'up' } }, { sequence: `\x1b[1;2H`, expected: { name: 'home', shift: true } }, { sequence: `\x1b[1;5F`, expected: { name: 'end', ctrl: true } }, { sequence: `\x1b[1;1P`, expected: { name: 'f1' } }, { sequence: `\x1b[1;3Q`, expected: { name: 'f2', meta: true } }, + // Tilde Function Keys { sequence: `\x1b[3~`, expected: { name: 'delete' } }, { sequence: `\x1b[5~`, expected: { name: 'pageup' } }, { sequence: `\x1b[6~`, expected: { name: 'pagedown' } }, @@ -441,6 +450,7 @@ describe('KeypressContext', () => { sequence: `\x1b[D`, expected: { name: 'left', ctrl: false, meta: false, shift: false }, }, + // Legacy Home/End { sequence: `\x1b[H`, diff --git a/packages/cli/src/ui/contexts/KeypressContext.tsx b/packages/cli/src/ui/contexts/KeypressContext.tsx index 05c24626cb..1c88e128c7 100644 --- a/packages/cli/src/ui/contexts/KeypressContext.tsx +++ b/packages/cli/src/ui/contexts/KeypressContext.tsx @@ -366,13 +366,15 @@ function* emitKeys( // skip modifier if (ch === ';') { - ch = yield; - sequence += ch; - - // collect as many digits as possible - while (ch >= '0' && ch <= '9') { + while (ch === ';') { ch = yield; sequence += ch; + + // collect as many digits as possible + while (ch >= '0' && ch <= '9') { + ch = yield; + sequence += ch; + } } } else if (ch === '<') { // SGR mouse mode @@ -401,10 +403,17 @@ function* emitKeys( const cmd = sequence.slice(cmdStart); let match; - if ((match = /^(\d+)(?:;(\d+))?([~^$u])$/.exec(cmd))) { - code += match[1] + match[3]; - // Defaults to '1' if no modifier exists, resulting in a 0 modifier value - modifier = parseInt(match[2] ?? '1', 10) - 1; + if ((match = /^(\d+)(?:;(\d+))?(?:;(\d+))?([~^$u])$/.exec(cmd))) { + if (match[1] === '27' && match[3] && match[4] === '~') { + // modifyOtherKeys format: CSI 27 ; modifier ; key ~ + // Treat as CSI u: key + 'u' + code += match[3] + 'u'; + modifier = parseInt(match[2] ?? '1', 10) - 1; + } else { + code += match[1] + match[4]; + // Defaults to '1' if no modifier exists, resulting in a 0 modifier value + modifier = parseInt(match[2] ?? '1', 10) - 1; + } } else if ((match = /^(\d+)?(?:;(\d+))?([A-Za-z])$/.exec(cmd))) { code += match[3]; modifier = parseInt(match[2] ?? match[1] ?? '1', 10) - 1;