fix: arrow keys on windows (#661)

This commit is contained in:
Mingholy
2025-09-19 19:44:57 +08:00
committed by GitHub
parent da0863b943
commit 9a56560eb4
2 changed files with 98 additions and 34 deletions

View File

@@ -526,7 +526,7 @@ describe('KeypressContext - Kitty Protocol', () => {
}); });
await waitFor(() => { await waitFor(() => {
expect(keyHandler).toHaveBeenCalledTimes(2); // 1 paste event + 1 paste event for 'after' expect(keyHandler).toHaveBeenCalledTimes(6); // 1 paste event + 5 individual chars for 'after'
}); });
// Should emit paste event first // Should emit paste event first
@@ -538,12 +538,40 @@ describe('KeypressContext - Kitty Protocol', () => {
}), }),
); );
// Then process 'after' as a paste event (since it's > 2 chars) // Then process 'after' as individual characters (since it doesn't contain return)
expect(keyHandler).toHaveBeenNthCalledWith( expect(keyHandler).toHaveBeenNthCalledWith(
2, 2,
expect.objectContaining({ expect.objectContaining({
paste: true, name: 'a',
sequence: 'after', paste: false,
}),
);
expect(keyHandler).toHaveBeenNthCalledWith(
3,
expect.objectContaining({
name: 'f',
paste: false,
}),
);
expect(keyHandler).toHaveBeenNthCalledWith(
4,
expect.objectContaining({
name: 't',
paste: false,
}),
);
expect(keyHandler).toHaveBeenNthCalledWith(
5,
expect.objectContaining({
name: 'e',
paste: false,
}),
);
expect(keyHandler).toHaveBeenNthCalledWith(
6,
expect.objectContaining({
name: 'r',
paste: false,
}), }),
); );
}); });
@@ -571,7 +599,7 @@ describe('KeypressContext - Kitty Protocol', () => {
}); });
await waitFor(() => { await waitFor(() => {
expect(keyHandler).toHaveBeenCalledTimes(14); // Adjusted based on actual behavior expect(keyHandler).toHaveBeenCalledTimes(16); // 5 + 1 + 6 + 1 + 3 = 16 calls
}); });
// Check the sequence: 'start' (5 chars) + paste1 + 'middle' (6 chars) + paste2 + 'end' (3 chars as paste) // Check the sequence: 'start' (5 chars) + paste1 + 'middle' (6 chars) + paste2 + 'end' (3 chars as paste)
@@ -643,13 +671,18 @@ describe('KeypressContext - Kitty Protocol', () => {
}), }),
); );
// 'end' as paste event (since it's > 2 chars) // 'end' as individual characters (since it doesn't contain return)
expect(keyHandler).toHaveBeenNthCalledWith( expect(keyHandler).toHaveBeenNthCalledWith(
callIndex++, callIndex++,
expect.objectContaining({ expect.objectContaining({ name: 'e' }),
paste: true, );
sequence: 'end', expect(keyHandler).toHaveBeenNthCalledWith(
}), callIndex++,
expect.objectContaining({ name: 'n' }),
);
expect(keyHandler).toHaveBeenNthCalledWith(
callIndex++,
expect.objectContaining({ name: 'd' }),
); );
}); });
@@ -738,16 +771,18 @@ describe('KeypressContext - Kitty Protocol', () => {
}); });
await waitFor(() => { await waitFor(() => {
// With the current implementation, fragmented data gets processed differently // With the current implementation, fragmented paste markers get reconstructed
// The first fragment '\x1b[20' gets processed as individual characters // into a single paste event for 'content'
// The second fragment '0~content\x1b[2' gets processed as paste + individual chars expect(keyHandler).toHaveBeenCalledTimes(1);
// The third fragment '01~' gets processed as individual characters
expect(keyHandler).toHaveBeenCalled();
}); });
// The current implementation processes fragmented paste markers as separate events // Should reconstruct the fragmented paste markers into a single paste event
// rather than reconstructing them into a single paste event expect(keyHandler).toHaveBeenCalledWith(
expect(keyHandler.mock.calls.length).toBeGreaterThan(1); expect.objectContaining({
paste: true,
sequence: 'content',
}),
);
}); });
}); });
@@ -851,19 +886,38 @@ describe('KeypressContext - Kitty Protocol', () => {
stdin.emit('data', Buffer.from('lo')); stdin.emit('data', Buffer.from('lo'));
}); });
// With the current implementation, data is processed as it arrives // With the current implementation, data is processed as individual characters
// First chunk 'hel' is treated as paste (multi-character) // since 'hel' doesn't contain return (0x0d)
expect(keyHandler).toHaveBeenNthCalledWith( expect(keyHandler).toHaveBeenNthCalledWith(
1, 1,
expect.objectContaining({ expect.objectContaining({
paste: true, name: 'h',
sequence: 'hel', sequence: 'h',
paste: false,
}), }),
); );
// Second chunk 'lo' is processed as individual characters
expect(keyHandler).toHaveBeenNthCalledWith( expect(keyHandler).toHaveBeenNthCalledWith(
2, 2,
expect.objectContaining({
name: 'e',
sequence: 'e',
paste: false,
}),
);
expect(keyHandler).toHaveBeenNthCalledWith(
3,
expect.objectContaining({
name: 'l',
sequence: 'l',
paste: false,
}),
);
// Second chunk 'lo' is also processed as individual characters
expect(keyHandler).toHaveBeenNthCalledWith(
4,
expect.objectContaining({ expect.objectContaining({
name: 'l', name: 'l',
sequence: 'l', sequence: 'l',
@@ -872,7 +926,7 @@ describe('KeypressContext - Kitty Protocol', () => {
); );
expect(keyHandler).toHaveBeenNthCalledWith( expect(keyHandler).toHaveBeenNthCalledWith(
3, 5,
expect.objectContaining({ expect.objectContaining({
name: 'o', name: 'o',
sequence: 'o', sequence: 'o',
@@ -880,7 +934,7 @@ describe('KeypressContext - Kitty Protocol', () => {
}), }),
); );
expect(keyHandler).toHaveBeenCalledTimes(3); expect(keyHandler).toHaveBeenCalledTimes(5);
} finally { } finally {
vi.useRealTimers(); vi.useRealTimers();
} }
@@ -907,14 +961,20 @@ describe('KeypressContext - Kitty Protocol', () => {
}); });
// Should flush immediately without waiting for timeout // Should flush immediately without waiting for timeout
// Large data gets treated as paste event // Large data without return gets treated as individual characters
expect(keyHandler).toHaveBeenCalledTimes(1); expect(keyHandler).toHaveBeenCalledTimes(65);
expect(keyHandler).toHaveBeenCalledWith(
expect.objectContaining({ // Each character should be processed individually
paste: true, for (let i = 0; i < 65; i++) {
sequence: largeData, expect(keyHandler).toHaveBeenNthCalledWith(
}), i + 1,
); expect.objectContaining({
name: 'x',
sequence: 'x',
paste: false,
}),
);
}
// Advancing timer should not cause additional calls // Advancing timer should not cause additional calls
const callCountBefore = keyHandler.mock.calls.length; const callCountBefore = keyHandler.mock.calls.length;

View File

@@ -407,7 +407,11 @@ export function KeypressProvider({
return; return;
} }
if (rawDataBuffer.length <= 2 || isPaste) { if (
(rawDataBuffer.length <= 2 && rawDataBuffer.includes(0x0d)) ||
!rawDataBuffer.includes(0x0d) ||
isPaste
) {
keypressStream.write(rawDataBuffer); keypressStream.write(rawDataBuffer);
} else { } else {
// Flush raw data buffer as a paste event // Flush raw data buffer as a paste event