mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-04-30 12:40:44 +00:00
fix(keyboard): handle kitty keypad private-use keycodes
This commit is contained in:
parent
fcebd14a90
commit
8a0189c32d
2 changed files with 171 additions and 1 deletions
|
|
@ -1369,6 +1369,105 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('Kitty keypad private-use keys', () => {
|
||||
it.each([
|
||||
{ keyCode: 57399, digit: '0' },
|
||||
{ keyCode: 57400, digit: '1' },
|
||||
{ keyCode: 57401, digit: '2' },
|
||||
{ keyCode: 57402, digit: '3' },
|
||||
{ keyCode: 57403, digit: '4' },
|
||||
{ keyCode: 57404, digit: '5' },
|
||||
{ keyCode: 57405, digit: '6' },
|
||||
{ keyCode: 57406, digit: '7' },
|
||||
{ keyCode: 57407, digit: '8' },
|
||||
{ keyCode: 57408, digit: '9' },
|
||||
])(
|
||||
'parses kitty keypad digit keyCode $keyCode as "$digit"',
|
||||
({ keyCode, digit }) => {
|
||||
const keyHandler = vi.fn();
|
||||
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
||||
act(() => result.current.subscribe(keyHandler));
|
||||
|
||||
act(() => stdin.sendKittySequence(`\x1b[${keyCode}u`));
|
||||
|
||||
expect(keyHandler).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
name: digit,
|
||||
sequence: digit,
|
||||
kittyProtocol: true,
|
||||
}),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
{ keyCode: 57409, char: '.' },
|
||||
{ keyCode: 57410, char: '/' },
|
||||
{ keyCode: 57411, char: '*' },
|
||||
{ keyCode: 57412, char: '-' },
|
||||
{ keyCode: 57413, char: '+' },
|
||||
{ keyCode: 57415, char: '=' },
|
||||
{ keyCode: 57416, char: ',' },
|
||||
])(
|
||||
'parses kitty keypad printable keyCode $keyCode as "$char"',
|
||||
({ keyCode, char }) => {
|
||||
const keyHandler = vi.fn();
|
||||
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
||||
act(() => result.current.subscribe(keyHandler));
|
||||
|
||||
act(() => stdin.sendKittySequence(`\x1b[${keyCode}u`));
|
||||
|
||||
expect(keyHandler).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
name: char,
|
||||
sequence: char,
|
||||
kittyProtocol: true,
|
||||
}),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
{ keyCode: 57417, name: 'left' },
|
||||
{ keyCode: 57418, name: 'right' },
|
||||
{ keyCode: 57419, name: 'up' },
|
||||
{ keyCode: 57420, name: 'down' },
|
||||
{ keyCode: 57421, name: 'pageup' },
|
||||
{ keyCode: 57422, name: 'pagedown' },
|
||||
{ keyCode: 57423, name: 'home' },
|
||||
{ keyCode: 57424, name: 'end' },
|
||||
{ keyCode: 57425, name: 'insert' },
|
||||
{ keyCode: 57426, name: 'delete' },
|
||||
])(
|
||||
'parses kitty keypad functional keyCode $keyCode as $name',
|
||||
({ keyCode, name }) => {
|
||||
const keyHandler = vi.fn();
|
||||
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
||||
act(() => result.current.subscribe(keyHandler));
|
||||
|
||||
act(() => stdin.sendKittySequence(`\x1b[${keyCode};5u`));
|
||||
|
||||
expect(keyHandler).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
name,
|
||||
ctrl: true,
|
||||
kittyProtocol: true,
|
||||
}),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it('does not emit a placeholder for unmapped private-use keyCodes', () => {
|
||||
const keyHandler = vi.fn();
|
||||
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
||||
act(() => result.current.subscribe(keyHandler));
|
||||
|
||||
act(() => stdin.sendKittySequence(`\x1b[57398u`));
|
||||
|
||||
expect(keyHandler).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Shift+Tab forms', () => {
|
||||
it.each([
|
||||
{ sequence: `\x1b[Z`, description: 'legacy reverse Tab' },
|
||||
|
|
|
|||
|
|
@ -47,6 +47,39 @@ export const DRAG_COMPLETION_TIMEOUT_MS = 100; // Broadcast full path after 100m
|
|||
export const SINGLE_QUOTE = "'";
|
||||
export const DOUBLE_QUOTE = '"';
|
||||
|
||||
const KITTY_KEYPAD_PRINTABLE_KEYCODE_TO_CHAR: Record<number, string> = {
|
||||
57399: '0',
|
||||
57400: '1',
|
||||
57401: '2',
|
||||
57402: '3',
|
||||
57403: '4',
|
||||
57404: '5',
|
||||
57405: '6',
|
||||
57406: '7',
|
||||
57407: '8',
|
||||
57408: '9',
|
||||
57409: '.',
|
||||
57410: '/',
|
||||
57411: '*',
|
||||
57412: '-',
|
||||
57413: '+',
|
||||
57415: '=',
|
||||
57416: ',',
|
||||
};
|
||||
|
||||
const KITTY_KEYPAD_FUNCTIONAL_KEYCODE_TO_NAME: Record<number, string> = {
|
||||
57417: 'left',
|
||||
57418: 'right',
|
||||
57419: 'up',
|
||||
57420: 'down',
|
||||
57421: 'pageup',
|
||||
57422: 'pagedown',
|
||||
57423: 'home',
|
||||
57424: 'end',
|
||||
57425: 'insert',
|
||||
57426: 'delete',
|
||||
};
|
||||
|
||||
export interface Key {
|
||||
name: string;
|
||||
ctrl: boolean;
|
||||
|
|
@ -332,14 +365,52 @@ export function KeypressProvider({
|
|||
};
|
||||
}
|
||||
|
||||
if (!ctrl) {
|
||||
const keypadChar = KITTY_KEYPAD_PRINTABLE_KEYCODE_TO_CHAR[keyCode];
|
||||
if (keypadChar) {
|
||||
return {
|
||||
key: {
|
||||
name: keypadChar,
|
||||
ctrl: false,
|
||||
meta: alt,
|
||||
shift,
|
||||
paste: false,
|
||||
sequence: keypadChar,
|
||||
kittyProtocol: true,
|
||||
},
|
||||
length: m[0].length,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const keypadName = KITTY_KEYPAD_FUNCTIONAL_KEYCODE_TO_NAME[keyCode];
|
||||
if (keypadName) {
|
||||
return {
|
||||
key: {
|
||||
name: keypadName,
|
||||
ctrl,
|
||||
meta: alt,
|
||||
shift,
|
||||
paste: false,
|
||||
sequence: buffer.slice(0, m[0].length),
|
||||
kittyProtocol: true,
|
||||
},
|
||||
length: m[0].length,
|
||||
};
|
||||
}
|
||||
|
||||
// Printable CSI-u keys (including space) should behave like regular
|
||||
// character input so downstream text inputs receive the literal char.
|
||||
// Kitty uses the Unicode private use area for some functional keys
|
||||
// such as keypad events, so exclude that range from generic printable
|
||||
// conversion and handle mapped keys explicitly above.
|
||||
if (
|
||||
terminator === 'u' &&
|
||||
!ctrl &&
|
||||
keyCode >= 32 &&
|
||||
keyCode !== 127 &&
|
||||
keyCode <= 0x10ffff
|
||||
keyCode <= 0x10ffff &&
|
||||
!(keyCode >= 0xe000 && keyCode <= 0xf8ff)
|
||||
) {
|
||||
const char = String.fromCodePoint(keyCode);
|
||||
const printableName =
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue