mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-05-04 14:40:45 +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_SUFFIX = `${ESC}[201~`;
|
||||
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 DOUBLE_QUOTE = '"';
|
||||
|
||||
|
|
@ -172,12 +177,44 @@ export function KeypressProvider({
|
|||
|
||||
let isPaste = false;
|
||||
let pasteBuffer = Buffer.alloc(0);
|
||||
let kittySequenceBuffer = '';
|
||||
const kittySequenceBufferRef = { current: '' };
|
||||
let kittySequenceTimeout: NodeJS.Timeout | null = null;
|
||||
let backslashTimeout: NodeJS.Timeout | null = null;
|
||||
let waitingForEnterAfterBackslash = false;
|
||||
let rawDataBuffer = Buffer.alloc(0);
|
||||
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 printableName =
|
||||
char === ' '
|
||||
|
|
@ -661,13 +698,13 @@ export function KeypressProvider({
|
|||
(key.ctrl && key.name === 'c') ||
|
||||
key.sequence === `${ESC}${KITTY_CTRL_C}`
|
||||
) {
|
||||
if (kittySequenceBuffer && debugKeystrokeLogging) {
|
||||
if (kittySequenceBufferRef.current && debugKeystrokeLogging) {
|
||||
debugLogger.debug(
|
||||
'[DEBUG] Kitty buffer cleared on Ctrl+C:',
|
||||
kittySequenceBuffer,
|
||||
kittySequenceBufferRef.current,
|
||||
);
|
||||
}
|
||||
kittySequenceBuffer = '';
|
||||
clearKittyBufferAndTimeout();
|
||||
if (key.sequence === `${ESC}${KITTY_CTRL_C}`) {
|
||||
broadcast({
|
||||
name: 'c',
|
||||
|
|
@ -686,19 +723,20 @@ export function KeypressProvider({
|
|||
|
||||
if (kittyProtocolEnabled) {
|
||||
if (
|
||||
kittySequenceBuffer ||
|
||||
kittySequenceBufferRef.current ||
|
||||
(key.sequence.startsWith(`${ESC}[`) &&
|
||||
!key.sequence.startsWith(PASTE_MODE_PREFIX) &&
|
||||
!key.sequence.startsWith(PASTE_MODE_SUFFIX) &&
|
||||
!key.sequence.startsWith(FOCUS_IN) &&
|
||||
!key.sequence.startsWith(FOCUS_OUT))
|
||||
) {
|
||||
kittySequenceBuffer += key.sequence;
|
||||
updateKittyBuffer(kittySequenceBufferRef.current + key.sequence);
|
||||
startKittyTimeout();
|
||||
|
||||
if (debugKeystrokeLogging) {
|
||||
debugLogger.debug(
|
||||
'[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
|
||||
// (ESC[) so that a following valid sequence can still be parsed.
|
||||
let bufferedInputHandled = false;
|
||||
while (kittySequenceBuffer) {
|
||||
const parsed = parseKittyPrefix(kittySequenceBuffer);
|
||||
while (kittySequenceBufferRef.current) {
|
||||
const parsed = parseKittyPrefix(kittySequenceBufferRef.current);
|
||||
if (parsed) {
|
||||
if (debugKeystrokeLogging) {
|
||||
const parsedSequence = kittySequenceBuffer.slice(
|
||||
const parsedSequence = kittySequenceBufferRef.current.slice(
|
||||
0,
|
||||
parsed.length,
|
||||
);
|
||||
if (kittySequenceBuffer.length > parsed.length) {
|
||||
if (kittySequenceBufferRef.current.length > parsed.length) {
|
||||
debugLogger.debug(
|
||||
'[DEBUG] Kitty sequence parsed successfully (prefix):',
|
||||
parsedSequence,
|
||||
|
|
@ -728,29 +766,45 @@ export function KeypressProvider({
|
|||
}
|
||||
}
|
||||
// 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);
|
||||
bufferedInputHandled = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
const completeUnsupportedCsiLength =
|
||||
getCompleteCsiSequenceLength(kittySequenceBuffer);
|
||||
const completeUnsupportedCsiLength = getCompleteCsiSequenceLength(
|
||||
kittySequenceBufferRef.current,
|
||||
);
|
||||
if (completeUnsupportedCsiLength) {
|
||||
if (debugKeystrokeLogging) {
|
||||
debugLogger.debug(
|
||||
'[DEBUG] Dropping unsupported complete CSI sequence:',
|
||||
kittySequenceBuffer.slice(0, completeUnsupportedCsiLength),
|
||||
kittySequenceBufferRef.current.slice(
|
||||
0,
|
||||
completeUnsupportedCsiLength,
|
||||
),
|
||||
);
|
||||
}
|
||||
kittySequenceBuffer = kittySequenceBuffer.slice(
|
||||
completeUnsupportedCsiLength,
|
||||
updateKittyBuffer(
|
||||
kittySequenceBufferRef.current.slice(
|
||||
completeUnsupportedCsiLength,
|
||||
),
|
||||
);
|
||||
if (!kittySequenceBufferRef.current) {
|
||||
clearKittyTimeout();
|
||||
}
|
||||
bufferedInputHandled = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
const plainTextPrefix = parsePlainTextPrefix(kittySequenceBuffer);
|
||||
const plainTextPrefix = parsePlainTextPrefix(
|
||||
kittySequenceBufferRef.current,
|
||||
);
|
||||
if (plainTextPrefix) {
|
||||
if (debugKeystrokeLogging) {
|
||||
debugLogger.debug(
|
||||
|
|
@ -758,24 +812,35 @@ export function KeypressProvider({
|
|||
plainTextPrefix.key.sequence,
|
||||
);
|
||||
}
|
||||
kittySequenceBuffer = kittySequenceBuffer.slice(
|
||||
plainTextPrefix.length,
|
||||
updateKittyBuffer(
|
||||
kittySequenceBufferRef.current.slice(plainTextPrefix.length),
|
||||
);
|
||||
if (!kittySequenceBufferRef.current) {
|
||||
clearKittyTimeout();
|
||||
}
|
||||
broadcast(plainTextPrefix.key);
|
||||
bufferedInputHandled = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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 (debugKeystrokeLogging) {
|
||||
debugLogger.debug(
|
||||
'[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;
|
||||
continue;
|
||||
}
|
||||
|
|
@ -784,27 +849,29 @@ export function KeypressProvider({
|
|||
if (bufferedInputHandled) return;
|
||||
|
||||
if (config?.getDebugMode() || debugKeystrokeLogging) {
|
||||
const codes = Array.from(kittySequenceBuffer).map((ch) =>
|
||||
ch.charCodeAt(0),
|
||||
const codes = Array.from(kittySequenceBufferRef.current).map(
|
||||
(ch: string) => ch.charCodeAt(0),
|
||||
);
|
||||
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) {
|
||||
debugLogger.debug(
|
||||
'[DEBUG] Kitty buffer overflow, clearing:',
|
||||
kittySequenceBuffer,
|
||||
kittySequenceBufferRef.current,
|
||||
);
|
||||
}
|
||||
if (config) {
|
||||
const event = new KittySequenceOverflowEvent(
|
||||
kittySequenceBuffer.length,
|
||||
kittySequenceBuffer,
|
||||
kittySequenceBufferRef.current.length,
|
||||
kittySequenceBufferRef.current,
|
||||
);
|
||||
logKittySequenceOverflow(config, event);
|
||||
}
|
||||
kittySequenceBuffer = '';
|
||||
clearKittyBufferAndTimeout();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
|
@ -965,6 +1032,8 @@ export function KeypressProvider({
|
|||
backslashTimeout = null;
|
||||
}
|
||||
|
||||
clearKittyBufferAndTimeout();
|
||||
|
||||
if (rawFlushTimeout) {
|
||||
clearTimeout(rawFlushTimeout);
|
||||
rawFlushTimeout = null;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue