mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-05-04 22:51:08 +00:00
Merge pull request #2612 from qqqys/fix/ime_vscode
fix(cli): enhance KeypressProvider with kitty sequence timeout manage…
This commit is contained in:
commit
449a421ca7
1 changed files with 99 additions and 30 deletions
|
|
@ -44,6 +44,11 @@ const ESC = '\u001B';
|
||||||
export const PASTE_MODE_PREFIX = `${ESC}[200~`;
|
export const PASTE_MODE_PREFIX = `${ESC}[200~`;
|
||||||
export const PASTE_MODE_SUFFIX = `${ESC}[201~`;
|
export const PASTE_MODE_SUFFIX = `${ESC}[201~`;
|
||||||
export const DRAG_COMPLETION_TIMEOUT_MS = 100; // Broadcast full path after 100ms if no more input
|
export const DRAG_COMPLETION_TIMEOUT_MS = 100; // Broadcast full path after 100ms if no more input
|
||||||
|
// Kitty sequence timeout: 200ms balances between:
|
||||||
|
// - Too short: prematurely clear valid sequences during slow input
|
||||||
|
// - Too long: delayed recovery from interrupted sequences (e.g., IME interruptions)
|
||||||
|
// Based on empirical testing with IME input patterns in VS Code integrated terminal.
|
||||||
|
export const KITTY_SEQUENCE_TIMEOUT_MS = 200;
|
||||||
export const SINGLE_QUOTE = "'";
|
export const SINGLE_QUOTE = "'";
|
||||||
export const DOUBLE_QUOTE = '"';
|
export const DOUBLE_QUOTE = '"';
|
||||||
|
|
||||||
|
|
@ -172,12 +177,44 @@ export function KeypressProvider({
|
||||||
|
|
||||||
let isPaste = false;
|
let isPaste = false;
|
||||||
let pasteBuffer = Buffer.alloc(0);
|
let pasteBuffer = Buffer.alloc(0);
|
||||||
let kittySequenceBuffer = '';
|
const kittySequenceBufferRef = { current: '' };
|
||||||
|
let kittySequenceTimeout: NodeJS.Timeout | null = null;
|
||||||
let backslashTimeout: NodeJS.Timeout | null = null;
|
let backslashTimeout: NodeJS.Timeout | null = null;
|
||||||
let waitingForEnterAfterBackslash = false;
|
let waitingForEnterAfterBackslash = false;
|
||||||
let rawDataBuffer = Buffer.alloc(0);
|
let rawDataBuffer = Buffer.alloc(0);
|
||||||
let rawFlushTimeout: NodeJS.Timeout | null = null;
|
let rawFlushTimeout: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
|
const updateKittyBuffer = (value: string) => {
|
||||||
|
kittySequenceBufferRef.current = value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearKittyTimeout = () => {
|
||||||
|
if (kittySequenceTimeout) {
|
||||||
|
clearTimeout(kittySequenceTimeout);
|
||||||
|
kittySequenceTimeout = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const startKittyTimeout = () => {
|
||||||
|
clearKittyTimeout();
|
||||||
|
kittySequenceTimeout = setTimeout(() => {
|
||||||
|
if (kittySequenceBufferRef.current) {
|
||||||
|
if (debugKeystrokeLogging) {
|
||||||
|
debugLogger.debug(
|
||||||
|
'[DEBUG] Kitty buffer timeout, clearing:',
|
||||||
|
kittySequenceBufferRef.current,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
kittySequenceBufferRef.current = '';
|
||||||
|
}
|
||||||
|
}, KITTY_SEQUENCE_TIMEOUT_MS);
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearKittyBufferAndTimeout = () => {
|
||||||
|
clearKittyTimeout();
|
||||||
|
kittySequenceBufferRef.current = '';
|
||||||
|
};
|
||||||
|
|
||||||
const createPrintableKey = (char: string): Key => {
|
const createPrintableKey = (char: string): Key => {
|
||||||
const printableName =
|
const printableName =
|
||||||
char === ' '
|
char === ' '
|
||||||
|
|
@ -661,13 +698,13 @@ export function KeypressProvider({
|
||||||
(key.ctrl && key.name === 'c') ||
|
(key.ctrl && key.name === 'c') ||
|
||||||
key.sequence === `${ESC}${KITTY_CTRL_C}`
|
key.sequence === `${ESC}${KITTY_CTRL_C}`
|
||||||
) {
|
) {
|
||||||
if (kittySequenceBuffer && debugKeystrokeLogging) {
|
if (kittySequenceBufferRef.current && debugKeystrokeLogging) {
|
||||||
debugLogger.debug(
|
debugLogger.debug(
|
||||||
'[DEBUG] Kitty buffer cleared on Ctrl+C:',
|
'[DEBUG] Kitty buffer cleared on Ctrl+C:',
|
||||||
kittySequenceBuffer,
|
kittySequenceBufferRef.current,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
kittySequenceBuffer = '';
|
clearKittyBufferAndTimeout();
|
||||||
if (key.sequence === `${ESC}${KITTY_CTRL_C}`) {
|
if (key.sequence === `${ESC}${KITTY_CTRL_C}`) {
|
||||||
broadcast({
|
broadcast({
|
||||||
name: 'c',
|
name: 'c',
|
||||||
|
|
@ -686,19 +723,20 @@ export function KeypressProvider({
|
||||||
|
|
||||||
if (kittyProtocolEnabled) {
|
if (kittyProtocolEnabled) {
|
||||||
if (
|
if (
|
||||||
kittySequenceBuffer ||
|
kittySequenceBufferRef.current ||
|
||||||
(key.sequence.startsWith(`${ESC}[`) &&
|
(key.sequence.startsWith(`${ESC}[`) &&
|
||||||
!key.sequence.startsWith(PASTE_MODE_PREFIX) &&
|
!key.sequence.startsWith(PASTE_MODE_PREFIX) &&
|
||||||
!key.sequence.startsWith(PASTE_MODE_SUFFIX) &&
|
!key.sequence.startsWith(PASTE_MODE_SUFFIX) &&
|
||||||
!key.sequence.startsWith(FOCUS_IN) &&
|
!key.sequence.startsWith(FOCUS_IN) &&
|
||||||
!key.sequence.startsWith(FOCUS_OUT))
|
!key.sequence.startsWith(FOCUS_OUT))
|
||||||
) {
|
) {
|
||||||
kittySequenceBuffer += key.sequence;
|
updateKittyBuffer(kittySequenceBufferRef.current + key.sequence);
|
||||||
|
startKittyTimeout();
|
||||||
|
|
||||||
if (debugKeystrokeLogging) {
|
if (debugKeystrokeLogging) {
|
||||||
debugLogger.debug(
|
debugLogger.debug(
|
||||||
'[DEBUG] Kitty buffer accumulating:',
|
'[DEBUG] Kitty buffer accumulating:',
|
||||||
kittySequenceBuffer,
|
kittySequenceBufferRef.current,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -707,15 +745,15 @@ export function KeypressProvider({
|
||||||
// prefix is incomplete or invalid, skip to the next CSI introducer
|
// prefix is incomplete or invalid, skip to the next CSI introducer
|
||||||
// (ESC[) so that a following valid sequence can still be parsed.
|
// (ESC[) so that a following valid sequence can still be parsed.
|
||||||
let bufferedInputHandled = false;
|
let bufferedInputHandled = false;
|
||||||
while (kittySequenceBuffer) {
|
while (kittySequenceBufferRef.current) {
|
||||||
const parsed = parseKittyPrefix(kittySequenceBuffer);
|
const parsed = parseKittyPrefix(kittySequenceBufferRef.current);
|
||||||
if (parsed) {
|
if (parsed) {
|
||||||
if (debugKeystrokeLogging) {
|
if (debugKeystrokeLogging) {
|
||||||
const parsedSequence = kittySequenceBuffer.slice(
|
const parsedSequence = kittySequenceBufferRef.current.slice(
|
||||||
0,
|
0,
|
||||||
parsed.length,
|
parsed.length,
|
||||||
);
|
);
|
||||||
if (kittySequenceBuffer.length > parsed.length) {
|
if (kittySequenceBufferRef.current.length > parsed.length) {
|
||||||
debugLogger.debug(
|
debugLogger.debug(
|
||||||
'[DEBUG] Kitty sequence parsed successfully (prefix):',
|
'[DEBUG] Kitty sequence parsed successfully (prefix):',
|
||||||
parsedSequence,
|
parsedSequence,
|
||||||
|
|
@ -728,29 +766,45 @@ export function KeypressProvider({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Consume the parsed prefix and broadcast it.
|
// Consume the parsed prefix and broadcast it.
|
||||||
kittySequenceBuffer = kittySequenceBuffer.slice(parsed.length);
|
updateKittyBuffer(
|
||||||
|
kittySequenceBufferRef.current.slice(parsed.length),
|
||||||
|
);
|
||||||
|
if (!kittySequenceBufferRef.current) {
|
||||||
|
clearKittyTimeout();
|
||||||
|
}
|
||||||
broadcast(parsed.key);
|
broadcast(parsed.key);
|
||||||
bufferedInputHandled = true;
|
bufferedInputHandled = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const completeUnsupportedCsiLength =
|
const completeUnsupportedCsiLength = getCompleteCsiSequenceLength(
|
||||||
getCompleteCsiSequenceLength(kittySequenceBuffer);
|
kittySequenceBufferRef.current,
|
||||||
|
);
|
||||||
if (completeUnsupportedCsiLength) {
|
if (completeUnsupportedCsiLength) {
|
||||||
if (debugKeystrokeLogging) {
|
if (debugKeystrokeLogging) {
|
||||||
debugLogger.debug(
|
debugLogger.debug(
|
||||||
'[DEBUG] Dropping unsupported complete CSI sequence:',
|
'[DEBUG] Dropping unsupported complete CSI sequence:',
|
||||||
kittySequenceBuffer.slice(0, completeUnsupportedCsiLength),
|
kittySequenceBufferRef.current.slice(
|
||||||
|
0,
|
||||||
|
completeUnsupportedCsiLength,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
kittySequenceBuffer = kittySequenceBuffer.slice(
|
updateKittyBuffer(
|
||||||
completeUnsupportedCsiLength,
|
kittySequenceBufferRef.current.slice(
|
||||||
|
completeUnsupportedCsiLength,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
if (!kittySequenceBufferRef.current) {
|
||||||
|
clearKittyTimeout();
|
||||||
|
}
|
||||||
bufferedInputHandled = true;
|
bufferedInputHandled = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const plainTextPrefix = parsePlainTextPrefix(kittySequenceBuffer);
|
const plainTextPrefix = parsePlainTextPrefix(
|
||||||
|
kittySequenceBufferRef.current,
|
||||||
|
);
|
||||||
if (plainTextPrefix) {
|
if (plainTextPrefix) {
|
||||||
if (debugKeystrokeLogging) {
|
if (debugKeystrokeLogging) {
|
||||||
debugLogger.debug(
|
debugLogger.debug(
|
||||||
|
|
@ -758,24 +812,35 @@ export function KeypressProvider({
|
||||||
plainTextPrefix.key.sequence,
|
plainTextPrefix.key.sequence,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
kittySequenceBuffer = kittySequenceBuffer.slice(
|
updateKittyBuffer(
|
||||||
plainTextPrefix.length,
|
kittySequenceBufferRef.current.slice(plainTextPrefix.length),
|
||||||
);
|
);
|
||||||
|
if (!kittySequenceBufferRef.current) {
|
||||||
|
clearKittyTimeout();
|
||||||
|
}
|
||||||
broadcast(plainTextPrefix.key);
|
broadcast(plainTextPrefix.key);
|
||||||
bufferedInputHandled = true;
|
bufferedInputHandled = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look for the next potential CSI start beyond index 0
|
// Look for the next potential CSI start beyond index 0
|
||||||
const nextStart = kittySequenceBuffer.indexOf(`${ESC}[`, 1);
|
const nextStart = kittySequenceBufferRef.current.indexOf(
|
||||||
|
`${ESC}[`,
|
||||||
|
1,
|
||||||
|
);
|
||||||
if (nextStart > 0) {
|
if (nextStart > 0) {
|
||||||
if (debugKeystrokeLogging) {
|
if (debugKeystrokeLogging) {
|
||||||
debugLogger.debug(
|
debugLogger.debug(
|
||||||
'[DEBUG] Skipping incomplete/invalid CSI prefix:',
|
'[DEBUG] Skipping incomplete/invalid CSI prefix:',
|
||||||
kittySequenceBuffer.slice(0, nextStart),
|
kittySequenceBufferRef.current.slice(0, nextStart),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
kittySequenceBuffer = kittySequenceBuffer.slice(nextStart);
|
updateKittyBuffer(
|
||||||
|
kittySequenceBufferRef.current.slice(nextStart),
|
||||||
|
);
|
||||||
|
if (!kittySequenceBufferRef.current) {
|
||||||
|
clearKittyTimeout();
|
||||||
|
}
|
||||||
bufferedInputHandled = true;
|
bufferedInputHandled = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -784,27 +849,29 @@ export function KeypressProvider({
|
||||||
if (bufferedInputHandled) return;
|
if (bufferedInputHandled) return;
|
||||||
|
|
||||||
if (config?.getDebugMode() || debugKeystrokeLogging) {
|
if (config?.getDebugMode() || debugKeystrokeLogging) {
|
||||||
const codes = Array.from(kittySequenceBuffer).map((ch) =>
|
const codes = Array.from(kittySequenceBufferRef.current).map(
|
||||||
ch.charCodeAt(0),
|
(ch: string) => ch.charCodeAt(0),
|
||||||
);
|
);
|
||||||
debugLogger.warn('Kitty sequence buffer has char codes:', codes);
|
debugLogger.warn('Kitty sequence buffer has char codes:', codes);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (kittySequenceBuffer.length > MAX_KITTY_SEQUENCE_LENGTH) {
|
if (
|
||||||
|
kittySequenceBufferRef.current.length > MAX_KITTY_SEQUENCE_LENGTH
|
||||||
|
) {
|
||||||
if (debugKeystrokeLogging) {
|
if (debugKeystrokeLogging) {
|
||||||
debugLogger.debug(
|
debugLogger.debug(
|
||||||
'[DEBUG] Kitty buffer overflow, clearing:',
|
'[DEBUG] Kitty buffer overflow, clearing:',
|
||||||
kittySequenceBuffer,
|
kittySequenceBufferRef.current,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (config) {
|
if (config) {
|
||||||
const event = new KittySequenceOverflowEvent(
|
const event = new KittySequenceOverflowEvent(
|
||||||
kittySequenceBuffer.length,
|
kittySequenceBufferRef.current.length,
|
||||||
kittySequenceBuffer,
|
kittySequenceBufferRef.current,
|
||||||
);
|
);
|
||||||
logKittySequenceOverflow(config, event);
|
logKittySequenceOverflow(config, event);
|
||||||
}
|
}
|
||||||
kittySequenceBuffer = '';
|
clearKittyBufferAndTimeout();
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -965,6 +1032,8 @@ export function KeypressProvider({
|
||||||
backslashTimeout = null;
|
backslashTimeout = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clearKittyBufferAndTimeout();
|
||||||
|
|
||||||
if (rawFlushTimeout) {
|
if (rawFlushTimeout) {
|
||||||
clearTimeout(rawFlushTimeout);
|
clearTimeout(rawFlushTimeout);
|
||||||
rawFlushTimeout = null;
|
rawFlushTimeout = null;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue