mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-05-05 15:31:27 +00:00
fix(cli): add kitty sequence timeout management and buffer handling improvements in KeypressProvider
This commit is contained in:
parent
61fb9425a2
commit
7b1d294324
1 changed files with 66 additions and 45 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,15 +177,17 @@ export function KeypressProvider({
|
|||
|
||||
let isPaste = false;
|
||||
let pasteBuffer = Buffer.alloc(0);
|
||||
let kittySequenceBuffer = '';
|
||||
let kittySequenceTimeout: NodeJS.Timeout | null = null;
|
||||
const KITTY_SEQUENCE_TIMEOUT_MS = 200;
|
||||
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);
|
||||
|
|
@ -199,11 +206,15 @@ export function KeypressProvider({
|
|||
);
|
||||
}
|
||||
kittySequenceBufferRef.current = '';
|
||||
kittySequenceBuffer = '';
|
||||
}
|
||||
}, KITTY_SEQUENCE_TIMEOUT_MS);
|
||||
};
|
||||
|
||||
const clearKittyBufferAndTimeout = () => {
|
||||
clearKittyTimeout();
|
||||
kittySequenceBufferRef.current = '';
|
||||
};
|
||||
|
||||
const createPrintableKey = (char: string): Key => {
|
||||
const printableName =
|
||||
char === ' '
|
||||
|
|
@ -678,13 +689,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',
|
||||
|
|
@ -703,21 +714,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;
|
||||
kittySequenceBufferRef.current = kittySequenceBuffer;
|
||||
updateKittyBuffer(kittySequenceBufferRef.current + key.sequence);
|
||||
startKittyTimeout();
|
||||
|
||||
if (debugKeystrokeLogging) {
|
||||
debugLogger.debug(
|
||||
'[DEBUG] Kitty buffer accumulating:',
|
||||
kittySequenceBuffer,
|
||||
kittySequenceBufferRef.current,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -726,15 +736,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,
|
||||
|
|
@ -747,9 +757,10 @@ export function KeypressProvider({
|
|||
}
|
||||
}
|
||||
// Consume the parsed prefix and broadcast it.
|
||||
kittySequenceBuffer = kittySequenceBuffer.slice(parsed.length);
|
||||
kittySequenceBufferRef.current = kittySequenceBuffer;
|
||||
if (!kittySequenceBuffer) {
|
||||
updateKittyBuffer(
|
||||
kittySequenceBufferRef.current.slice(parsed.length),
|
||||
);
|
||||
if (!kittySequenceBufferRef.current) {
|
||||
clearKittyTimeout();
|
||||
}
|
||||
broadcast(parsed.key);
|
||||
|
|
@ -757,27 +768,34 @@ export function KeypressProvider({
|
|||
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,
|
||||
),
|
||||
);
|
||||
kittySequenceBufferRef.current = kittySequenceBuffer;
|
||||
if (!kittySequenceBuffer) {
|
||||
if (!kittySequenceBufferRef.current) {
|
||||
clearKittyTimeout();
|
||||
}
|
||||
bufferedInputHandled = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
const plainTextPrefix = parsePlainTextPrefix(kittySequenceBuffer);
|
||||
const plainTextPrefix = parsePlainTextPrefix(
|
||||
kittySequenceBufferRef.current,
|
||||
);
|
||||
if (plainTextPrefix) {
|
||||
if (debugKeystrokeLogging) {
|
||||
debugLogger.debug(
|
||||
|
|
@ -785,11 +803,10 @@ export function KeypressProvider({
|
|||
plainTextPrefix.key.sequence,
|
||||
);
|
||||
}
|
||||
kittySequenceBuffer = kittySequenceBuffer.slice(
|
||||
plainTextPrefix.length,
|
||||
updateKittyBuffer(
|
||||
kittySequenceBufferRef.current.slice(plainTextPrefix.length),
|
||||
);
|
||||
kittySequenceBufferRef.current = kittySequenceBuffer;
|
||||
if (!kittySequenceBuffer) {
|
||||
if (!kittySequenceBufferRef.current) {
|
||||
clearKittyTimeout();
|
||||
}
|
||||
broadcast(plainTextPrefix.key);
|
||||
|
|
@ -798,17 +815,21 @@ export function KeypressProvider({
|
|||
}
|
||||
|
||||
// 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);
|
||||
kittySequenceBufferRef.current = kittySequenceBuffer;
|
||||
if (!kittySequenceBuffer) {
|
||||
updateKittyBuffer(
|
||||
kittySequenceBufferRef.current.slice(nextStart),
|
||||
);
|
||||
if (!kittySequenceBufferRef.current) {
|
||||
clearKittyTimeout();
|
||||
}
|
||||
bufferedInputHandled = true;
|
||||
|
|
@ -819,29 +840,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 = '';
|
||||
kittySequenceBufferRef.current = '';
|
||||
clearKittyTimeout();
|
||||
clearKittyBufferAndTimeout();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
|
@ -1002,7 +1023,7 @@ export function KeypressProvider({
|
|||
backslashTimeout = null;
|
||||
}
|
||||
|
||||
clearKittyTimeout();
|
||||
clearKittyBufferAndTimeout();
|
||||
|
||||
if (rawFlushTimeout) {
|
||||
clearTimeout(rawFlushTimeout);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue