@vybestack/llxprt-code 0.8.0-nightly.260113.cd34be5f1 → 0.8.0-nightly.260114.c3308ac65
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -27
- package/dist/package.json +3 -3
- package/dist/src/commands/extensions.js +1 -0
- package/dist/src/commands/extensions.js.map +1 -1
- package/dist/src/config/config.d.ts +1 -1
- package/dist/src/config/config.js +35 -8
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/extension.d.ts +1 -0
- package/dist/src/config/extension.js +14 -5
- package/dist/src/config/extension.js.map +1 -1
- package/dist/src/config/extensions/extensionEnablement.d.ts +1 -1
- package/dist/src/config/extensions/extensionEnablement.js +3 -2
- package/dist/src/config/extensions/extensionEnablement.js.map +1 -1
- package/dist/src/config/extensions/extensionEnablement.test.js +10 -9
- package/dist/src/config/extensions/extensionEnablement.test.js.map +1 -1
- package/dist/src/config/extensions/github.js +3 -2
- package/dist/src/config/extensions/github.js.map +1 -1
- package/dist/src/config/settings.js +2 -2
- package/dist/src/config/settings.js.map +1 -1
- package/dist/src/config/settingsSchema.d.ts +17 -4
- package/dist/src/config/settingsSchema.js +18 -4
- package/dist/src/config/settingsSchema.js.map +1 -1
- package/dist/src/generated/git-commit.d.ts +1 -1
- package/dist/src/generated/git-commit.js +1 -1
- package/dist/src/nonInteractiveCli.js +19 -1
- package/dist/src/nonInteractiveCli.js.map +1 -1
- package/dist/src/runtime/__tests__/profileApplication.bucket-failover.spec.js +16 -0
- package/dist/src/runtime/__tests__/profileApplication.bucket-failover.spec.js.map +1 -1
- package/dist/src/runtime/bucketFailover.js +9 -0
- package/dist/src/runtime/bucketFailover.js.map +1 -1
- package/dist/src/ui/AppContainer.js +22 -11
- package/dist/src/ui/AppContainer.js.map +1 -1
- package/dist/src/ui/commands/directoryCommand.js +1 -1
- package/dist/src/ui/commands/directoryCommand.js.map +1 -1
- package/dist/src/ui/commands/extensionsCommand.js +19 -10
- package/dist/src/ui/commands/extensionsCommand.js.map +1 -1
- package/dist/src/ui/commands/memoryCommand.js +1 -1
- package/dist/src/ui/commands/memoryCommand.js.map +1 -1
- package/dist/src/ui/commands/setCommand.js +21 -7
- package/dist/src/ui/commands/setCommand.js.map +1 -1
- package/dist/src/ui/components/HistoryItemDisplay.js +2 -1
- package/dist/src/ui/components/HistoryItemDisplay.js.map +1 -1
- package/dist/src/ui/components/InputPrompt.js +5 -6
- package/dist/src/ui/components/InputPrompt.js.map +1 -1
- package/dist/src/ui/components/views/ExtensionsList.d.ts +12 -0
- package/dist/src/ui/components/views/ExtensionsList.js +42 -0
- package/dist/src/ui/components/views/ExtensionsList.js.map +1 -0
- package/dist/src/ui/components/views/ExtensionsList.test.d.ts +6 -0
- package/dist/src/ui/components/views/ExtensionsList.test.js +111 -0
- package/dist/src/ui/components/views/ExtensionsList.test.js.map +1 -0
- package/dist/src/ui/containers/SessionController.js +1 -1
- package/dist/src/ui/containers/SessionController.js.map +1 -1
- package/dist/src/ui/contexts/KeypressContext.test.js +131 -477
- package/dist/src/ui/contexts/KeypressContext.test.js.map +1 -1
- package/dist/src/ui/hooks/useAutoAcceptIndicator.js +10 -0
- package/dist/src/ui/hooks/useAutoAcceptIndicator.js.map +1 -1
- package/dist/src/ui/hooks/useAutoAcceptIndicator.test.js +1 -0
- package/dist/src/ui/hooks/useAutoAcceptIndicator.test.js.map +1 -1
- package/dist/src/ui/hooks/useGeminiStream.js +39 -3
- package/dist/src/ui/hooks/useGeminiStream.js.map +1 -1
- package/dist/src/ui/hooks/useGitBranchName.js +4 -0
- package/dist/src/ui/hooks/useGitBranchName.js.map +1 -1
- package/dist/src/ui/hooks/useGitBranchName.test.js +15 -17
- package/dist/src/ui/hooks/useGitBranchName.test.js.map +1 -1
- package/dist/src/ui/hooks/useToolScheduler.test.js +6 -7
- package/dist/src/ui/hooks/useToolScheduler.test.js.map +1 -1
- package/dist/src/ui/types.d.ts +2 -1
- package/dist/src/ui/types.js.map +1 -1
- package/dist/src/utils/handleAutoUpdate.js +3 -3
- package/dist/src/zed-integration/zedIntegration.js +5 -10
- package/dist/src/zed-integration/zedIntegration.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +3 -3
|
@@ -15,30 +15,35 @@ vi.mock('ink', async (importOriginal) => {
|
|
|
15
15
|
useStdin: vi.fn(),
|
|
16
16
|
};
|
|
17
17
|
});
|
|
18
|
+
const PASTE_START = '\x1B[200~';
|
|
19
|
+
const PASTE_END = '\x1B[201~';
|
|
20
|
+
// readline will not emit most incomplete kitty sequences but it will give
|
|
21
|
+
// up on sequences like this where the modifier (135) has more than two digits.
|
|
22
|
+
const INCOMPLETE_KITTY_SEQUENCE = '\x1b[97;135';
|
|
18
23
|
class MockStdin extends EventEmitter {
|
|
19
24
|
isTTY = true;
|
|
20
25
|
isRaw = false;
|
|
21
26
|
setRawMode = vi.fn();
|
|
22
27
|
on = this.addListener;
|
|
23
28
|
removeListener = super.removeListener;
|
|
24
|
-
write = vi.fn();
|
|
25
29
|
resume = vi.fn();
|
|
26
30
|
pause = vi.fn();
|
|
27
|
-
|
|
31
|
+
write(text) {
|
|
32
|
+
this.emit('data', Buffer.from(text));
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Used to directly simulate keyPress events. Certain keypress events might
|
|
36
|
+
* be impossible to fire in certain versions of node. This allows us to
|
|
37
|
+
* sidestep readline entirely and just emit the keypress we want.
|
|
38
|
+
*/
|
|
28
39
|
pressKey(key) {
|
|
29
40
|
this.emit('keypress', null, key);
|
|
30
41
|
}
|
|
31
|
-
// Helper to simulate a kitty protocol sequence
|
|
32
42
|
sendKittySequence(sequence) {
|
|
33
|
-
this.
|
|
43
|
+
this.write(sequence);
|
|
34
44
|
}
|
|
35
|
-
// Helper to simulate a paste event
|
|
36
45
|
sendPaste(text) {
|
|
37
|
-
|
|
38
|
-
const PASTE_MODE_SUFFIX = `\x1b[201~`;
|
|
39
|
-
this.emit('data', Buffer.from(PASTE_MODE_PREFIX));
|
|
40
|
-
this.emit('data', Buffer.from(text));
|
|
41
|
-
this.emit('data', Buffer.from(PASTE_MODE_SUFFIX));
|
|
46
|
+
this.write(PASTE_START + text + PASTE_END);
|
|
42
47
|
}
|
|
43
48
|
}
|
|
44
49
|
describe('KeypressContext - Kitty Protocol', () => {
|
|
@@ -59,12 +64,10 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|
|
59
64
|
const { result } = renderHook(() => useKeypressContext(), {
|
|
60
65
|
wrapper: ({ children }) => wrapper({ children, kittyProtocolEnabled: true }),
|
|
61
66
|
});
|
|
62
|
-
act(() =>
|
|
63
|
-
result.current.subscribe(keyHandler);
|
|
64
|
-
});
|
|
67
|
+
act(() => result.current.subscribe(keyHandler));
|
|
65
68
|
// Send kitty protocol sequence for regular enter: ESC[13u
|
|
66
69
|
act(() => {
|
|
67
|
-
stdin.
|
|
70
|
+
stdin.write(`\x1b[13u`);
|
|
68
71
|
});
|
|
69
72
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
70
73
|
name: 'return',
|
|
@@ -79,12 +82,10 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|
|
79
82
|
const { result } = renderHook(() => useKeypressContext(), {
|
|
80
83
|
wrapper: ({ children }) => wrapper({ children, kittyProtocolEnabled: true }),
|
|
81
84
|
});
|
|
82
|
-
act(() =>
|
|
83
|
-
result.current.subscribe(keyHandler);
|
|
84
|
-
});
|
|
85
|
+
act(() => result.current.subscribe(keyHandler));
|
|
85
86
|
// Send kitty protocol sequence for numpad enter: ESC[57414u
|
|
86
87
|
act(() => {
|
|
87
|
-
stdin.
|
|
88
|
+
stdin.write(`\x1b[57414u`);
|
|
88
89
|
});
|
|
89
90
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
90
91
|
name: 'return',
|
|
@@ -99,12 +100,10 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|
|
99
100
|
const { result } = renderHook(() => useKeypressContext(), {
|
|
100
101
|
wrapper: ({ children }) => wrapper({ children, kittyProtocolEnabled: true }),
|
|
101
102
|
});
|
|
102
|
-
act(() =>
|
|
103
|
-
result.current.subscribe(keyHandler);
|
|
104
|
-
});
|
|
103
|
+
act(() => result.current.subscribe(keyHandler));
|
|
105
104
|
// Send kitty protocol sequence for numpad enter with Shift (modifier 2): ESC[57414;2u
|
|
106
105
|
act(() => {
|
|
107
|
-
stdin.
|
|
106
|
+
stdin.write(`\x1b[57414;2u`);
|
|
108
107
|
});
|
|
109
108
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
110
109
|
name: 'return',
|
|
@@ -119,13 +118,9 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|
|
119
118
|
const { result } = renderHook(() => useKeypressContext(), {
|
|
120
119
|
wrapper: ({ children }) => wrapper({ children, kittyProtocolEnabled: true }),
|
|
121
120
|
});
|
|
122
|
-
act(() =>
|
|
123
|
-
result.current.subscribe(keyHandler);
|
|
124
|
-
});
|
|
121
|
+
act(() => result.current.subscribe(keyHandler));
|
|
125
122
|
// Send kitty protocol sequence for numpad enter with Ctrl (modifier 5): ESC[57414;5u
|
|
126
|
-
act(() =>
|
|
127
|
-
stdin.sendKittySequence(`\x1b[57414;5u`);
|
|
128
|
-
});
|
|
123
|
+
act(() => stdin.write(`\x1b[57414;5u`));
|
|
129
124
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
130
125
|
name: 'return',
|
|
131
126
|
kittyProtocol: true,
|
|
@@ -139,12 +134,10 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|
|
139
134
|
const { result } = renderHook(() => useKeypressContext(), {
|
|
140
135
|
wrapper: ({ children }) => wrapper({ children, kittyProtocolEnabled: true }),
|
|
141
136
|
});
|
|
142
|
-
act(() =>
|
|
143
|
-
result.current.subscribe(keyHandler);
|
|
144
|
-
});
|
|
137
|
+
act(() => result.current.subscribe(keyHandler));
|
|
145
138
|
// Send kitty protocol sequence for numpad enter with Alt (modifier 3): ESC[57414;3u
|
|
146
139
|
act(() => {
|
|
147
|
-
stdin.
|
|
140
|
+
stdin.write(`\x1b[57414;3u`);
|
|
148
141
|
});
|
|
149
142
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
150
143
|
name: 'return',
|
|
@@ -159,12 +152,10 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|
|
159
152
|
const { result } = renderHook(() => useKeypressContext(), {
|
|
160
153
|
wrapper: ({ children }) => wrapper({ children, kittyProtocolEnabled: false }),
|
|
161
154
|
});
|
|
162
|
-
act(() =>
|
|
163
|
-
result.current.subscribe(keyHandler);
|
|
164
|
-
});
|
|
155
|
+
act(() => result.current.subscribe(keyHandler));
|
|
165
156
|
// Send kitty protocol sequence for numpad enter
|
|
166
157
|
act(() => {
|
|
167
|
-
stdin.
|
|
158
|
+
stdin.write(`\x1b[57414u`);
|
|
168
159
|
});
|
|
169
160
|
// When kitty protocol is disabled, the sequence should be passed through
|
|
170
161
|
// as individual keypresses, not recognized as a single enter key
|
|
@@ -180,12 +171,10 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|
|
180
171
|
const { result } = renderHook(() => useKeypressContext(), {
|
|
181
172
|
wrapper: ({ children }) => wrapper({ children, kittyProtocolEnabled: true }),
|
|
182
173
|
});
|
|
183
|
-
act(() =>
|
|
184
|
-
result.current.subscribe(keyHandler);
|
|
185
|
-
});
|
|
174
|
+
act(() => result.current.subscribe(keyHandler));
|
|
186
175
|
// Send kitty protocol sequence for escape: ESC[27u
|
|
187
176
|
act(() => {
|
|
188
|
-
stdin.
|
|
177
|
+
stdin.write('\x1b[27u');
|
|
189
178
|
});
|
|
190
179
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
191
180
|
name: 'escape',
|
|
@@ -199,7 +188,7 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|
|
199
188
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
200
189
|
act(() => result.current.subscribe(keyHandler));
|
|
201
190
|
act(() => {
|
|
202
|
-
stdin.
|
|
191
|
+
stdin.write(`\x1b[9u`);
|
|
203
192
|
});
|
|
204
193
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
205
194
|
name: 'tab',
|
|
@@ -213,7 +202,7 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|
|
213
202
|
act(() => result.current.subscribe(keyHandler));
|
|
214
203
|
// Modifier 2 is Shift
|
|
215
204
|
act(() => {
|
|
216
|
-
stdin.
|
|
205
|
+
stdin.write(`\x1b[9;2u`);
|
|
217
206
|
});
|
|
218
207
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
219
208
|
name: 'tab',
|
|
@@ -226,7 +215,7 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|
|
226
215
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
227
216
|
act(() => result.current.subscribe(keyHandler));
|
|
228
217
|
act(() => {
|
|
229
|
-
stdin.
|
|
218
|
+
stdin.write(`\x1b[127u`);
|
|
230
219
|
});
|
|
231
220
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
232
221
|
name: 'backspace',
|
|
@@ -240,7 +229,7 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|
|
240
229
|
act(() => result.current.subscribe(keyHandler));
|
|
241
230
|
// Modifier 3 is Alt/Option
|
|
242
231
|
act(() => {
|
|
243
|
-
stdin.
|
|
232
|
+
stdin.write(`\x1b[127;3u`);
|
|
244
233
|
});
|
|
245
234
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
246
235
|
name: 'backspace',
|
|
@@ -254,7 +243,7 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|
|
254
243
|
act(() => result.current.subscribe(keyHandler));
|
|
255
244
|
// Modifier 5 is Ctrl
|
|
256
245
|
act(() => {
|
|
257
|
-
stdin.
|
|
246
|
+
stdin.write(`\x1b[127;5u`);
|
|
258
247
|
});
|
|
259
248
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
260
249
|
name: 'backspace',
|
|
@@ -289,12 +278,12 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|
|
289
278
|
const { result } = renderHook(() => useKeypressContext(), {
|
|
290
279
|
wrapper,
|
|
291
280
|
});
|
|
292
|
-
act(() =>
|
|
293
|
-
result.current.subscribe(keyHandler);
|
|
294
|
-
});
|
|
281
|
+
act(() => result.current.subscribe(keyHandler));
|
|
295
282
|
// Simulate a bracketed paste event
|
|
296
283
|
act(() => {
|
|
297
|
-
stdin.
|
|
284
|
+
stdin.write(PASTE_START);
|
|
285
|
+
stdin.write(pastedText);
|
|
286
|
+
stdin.write(PASTE_END);
|
|
298
287
|
});
|
|
299
288
|
await waitFor(() => {
|
|
300
289
|
// Expect the handler to be called exactly once for the entire paste
|
|
@@ -448,12 +437,10 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|
|
448
437
|
const keyHandler = vi.fn();
|
|
449
438
|
const wrapper = ({ children }) => (_jsx(KeypressProvider, { kittyProtocolEnabled: true, debugKeystrokeLogging: false, children: children }));
|
|
450
439
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
451
|
-
act(() =>
|
|
452
|
-
result.current.subscribe(keyHandler);
|
|
453
|
-
});
|
|
440
|
+
act(() => result.current.subscribe(keyHandler));
|
|
454
441
|
// Send a kitty sequence
|
|
455
442
|
act(() => {
|
|
456
|
-
stdin.
|
|
443
|
+
stdin.write('\x1b[27u');
|
|
457
444
|
});
|
|
458
445
|
expect(keyHandler).toHaveBeenCalled();
|
|
459
446
|
expect(consoleLogSpy).not.toHaveBeenCalledWith(expect.stringContaining('[DEBUG] Kitty'));
|
|
@@ -462,13 +449,9 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|
|
462
449
|
const keyHandler = vi.fn();
|
|
463
450
|
const wrapper = ({ children }) => (_jsx(KeypressProvider, { kittyProtocolEnabled: true, debugKeystrokeLogging: true, children: children }));
|
|
464
451
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
465
|
-
act(() =>
|
|
466
|
-
result.current.subscribe(keyHandler);
|
|
467
|
-
});
|
|
452
|
+
act(() => result.current.subscribe(keyHandler));
|
|
468
453
|
// Send a complete kitty sequence for escape
|
|
469
|
-
act(() =>
|
|
470
|
-
stdin.sendKittySequence('\x1b[27u');
|
|
471
|
-
});
|
|
454
|
+
act(() => stdin.write('\x1b[27u'));
|
|
472
455
|
expect(consoleLogSpy).toHaveBeenCalledWith('[DEBUG] Kitty buffer accumulating:', expect.stringContaining('"\\u001b[27u"'));
|
|
473
456
|
const parsedCall = consoleLogSpy.mock.calls.find((args) => typeof args[0] === 'string' &&
|
|
474
457
|
args[0].includes('[DEBUG] Kitty sequence parsed successfully'));
|
|
@@ -479,44 +462,21 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|
|
479
462
|
const keyHandler = vi.fn();
|
|
480
463
|
const wrapper = ({ children }) => (_jsx(KeypressProvider, { kittyProtocolEnabled: true, debugKeystrokeLogging: true, children: children }));
|
|
481
464
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
482
|
-
act(() =>
|
|
483
|
-
result.current.subscribe(keyHandler);
|
|
484
|
-
});
|
|
465
|
+
act(() => result.current.subscribe(keyHandler));
|
|
485
466
|
// Send a long sequence starting with a valid kitty prefix to trigger overflow
|
|
486
467
|
const longSequence = '\x1b[1;' + '1'.repeat(100);
|
|
487
|
-
act(() =>
|
|
488
|
-
stdin.sendKittySequence(longSequence);
|
|
489
|
-
});
|
|
468
|
+
act(() => stdin.write(longSequence));
|
|
490
469
|
expect(consoleLogSpy).toHaveBeenCalledWith('[DEBUG] Kitty buffer overflow, clearing:', expect.any(String));
|
|
491
470
|
});
|
|
492
471
|
it('should log kitty buffer clear on Ctrl+C when debugKeystrokeLogging is true', async () => {
|
|
493
472
|
const keyHandler = vi.fn();
|
|
494
473
|
const wrapper = ({ children }) => (_jsx(KeypressProvider, { kittyProtocolEnabled: true, debugKeystrokeLogging: true, children: children }));
|
|
495
474
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
496
|
-
act(() =>
|
|
497
|
-
|
|
498
|
-
});
|
|
499
|
-
// Send incomplete kitty sequence
|
|
500
|
-
act(() => {
|
|
501
|
-
stdin.pressKey({
|
|
502
|
-
name: undefined,
|
|
503
|
-
ctrl: false,
|
|
504
|
-
meta: false,
|
|
505
|
-
shift: false,
|
|
506
|
-
sequence: '\x1b[1',
|
|
507
|
-
});
|
|
508
|
-
});
|
|
475
|
+
act(() => result.current.subscribe(keyHandler));
|
|
476
|
+
act(() => stdin.write(INCOMPLETE_KITTY_SEQUENCE));
|
|
509
477
|
// Send Ctrl+C
|
|
510
|
-
act(() =>
|
|
511
|
-
|
|
512
|
-
name: 'c',
|
|
513
|
-
ctrl: true,
|
|
514
|
-
meta: false,
|
|
515
|
-
shift: false,
|
|
516
|
-
sequence: '\x03',
|
|
517
|
-
});
|
|
518
|
-
});
|
|
519
|
-
expect(consoleLogSpy).toHaveBeenCalledWith('[DEBUG] Kitty buffer cleared on Ctrl+C:', '\x1b[1');
|
|
478
|
+
act(() => stdin.write('\x03'));
|
|
479
|
+
expect(consoleLogSpy).toHaveBeenCalledWith('[DEBUG] Kitty buffer cleared on Ctrl+C:', INCOMPLETE_KITTY_SEQUENCE);
|
|
520
480
|
// Verify Ctrl+C was handled
|
|
521
481
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
522
482
|
name: 'c',
|
|
@@ -527,24 +487,13 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|
|
527
487
|
const keyHandler = vi.fn();
|
|
528
488
|
const wrapper = ({ children }) => (_jsx(KeypressProvider, { kittyProtocolEnabled: true, debugKeystrokeLogging: true, children: children }));
|
|
529
489
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
530
|
-
act(() =>
|
|
531
|
-
result.current.subscribe(keyHandler);
|
|
532
|
-
});
|
|
490
|
+
act(() => result.current.subscribe(keyHandler));
|
|
533
491
|
// Send incomplete kitty sequence
|
|
534
|
-
|
|
535
|
-
act(() => {
|
|
536
|
-
stdin.pressKey({
|
|
537
|
-
name: undefined,
|
|
538
|
-
ctrl: false,
|
|
539
|
-
meta: false,
|
|
540
|
-
shift: false,
|
|
541
|
-
sequence,
|
|
542
|
-
});
|
|
543
|
-
});
|
|
492
|
+
act(() => stdin.write(INCOMPLETE_KITTY_SEQUENCE));
|
|
544
493
|
// Verify debug logging for accumulation
|
|
545
|
-
expect(consoleLogSpy).toHaveBeenCalledWith('[DEBUG] Kitty buffer accumulating:', JSON.stringify(
|
|
494
|
+
expect(consoleLogSpy).toHaveBeenCalledWith('[DEBUG] Kitty buffer accumulating:', JSON.stringify(INCOMPLETE_KITTY_SEQUENCE));
|
|
546
495
|
// Verify warning for char codes
|
|
547
|
-
expect(consoleWarnSpy).toHaveBeenCalledWith('Kitty sequence buffer has content:', JSON.stringify(
|
|
496
|
+
expect(consoleWarnSpy).toHaveBeenCalledWith('Kitty sequence buffer has content:', JSON.stringify(INCOMPLETE_KITTY_SEQUENCE));
|
|
548
497
|
});
|
|
549
498
|
});
|
|
550
499
|
describe('Parameterized functional keys', () => {
|
|
@@ -597,29 +546,17 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|
|
597
546
|
const keyHandler = vi.fn();
|
|
598
547
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
599
548
|
act(() => result.current.subscribe(keyHandler));
|
|
600
|
-
act(() => stdin.
|
|
549
|
+
act(() => stdin.write(sequence));
|
|
601
550
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining(expected));
|
|
602
551
|
});
|
|
603
552
|
});
|
|
604
|
-
describe('Shift+Tab forms', () => {
|
|
605
|
-
it.each([
|
|
606
|
-
{ sequence: `\x1b[Z`, description: 'legacy reverse Tab' },
|
|
607
|
-
{ sequence: `\x1b[1;2Z`, description: 'parameterized reverse Tab' },
|
|
608
|
-
])('should recognize $description "$sequence" as Shift+Tab', ({ sequence }) => {
|
|
609
|
-
const keyHandler = vi.fn();
|
|
610
|
-
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
611
|
-
act(() => result.current.subscribe(keyHandler));
|
|
612
|
-
act(() => stdin.sendKittySequence(sequence));
|
|
613
|
-
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({ name: 'tab', shift: true }));
|
|
614
|
-
});
|
|
615
|
-
});
|
|
616
553
|
describe('Double-tap and batching', () => {
|
|
617
554
|
it('should emit two delete events for double-tap CSI[3~', async () => {
|
|
618
555
|
const keyHandler = vi.fn();
|
|
619
556
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
620
557
|
act(() => result.current.subscribe(keyHandler));
|
|
621
|
-
act(() => stdin.
|
|
622
|
-
act(() => stdin.
|
|
558
|
+
act(() => stdin.write(`\x1b[3~`));
|
|
559
|
+
act(() => stdin.write(`\x1b[3~`));
|
|
623
560
|
expect(keyHandler).toHaveBeenNthCalledWith(1, expect.objectContaining({ name: 'delete' }));
|
|
624
561
|
expect(keyHandler).toHaveBeenNthCalledWith(2, expect.objectContaining({ name: 'delete' }));
|
|
625
562
|
});
|
|
@@ -627,7 +564,7 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|
|
627
564
|
const keyHandler = vi.fn();
|
|
628
565
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
629
566
|
act(() => result.current.subscribe(keyHandler));
|
|
630
|
-
act(() => stdin.
|
|
567
|
+
act(() => stdin.write(`\x1b[3~\x1b[5~`));
|
|
631
568
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({ name: 'delete' }));
|
|
632
569
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({ name: 'pageup' }));
|
|
633
570
|
});
|
|
@@ -638,15 +575,9 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|
|
638
575
|
// Incomplete ESC sequence then a complete Delete
|
|
639
576
|
act(() => {
|
|
640
577
|
// Provide an incomplete ESC sequence chunk with a real ESC character
|
|
641
|
-
stdin.
|
|
642
|
-
name: undefined,
|
|
643
|
-
ctrl: false,
|
|
644
|
-
meta: false,
|
|
645
|
-
shift: false,
|
|
646
|
-
sequence: '\x1b[1;',
|
|
647
|
-
});
|
|
578
|
+
stdin.write('\x1b[1;');
|
|
648
579
|
});
|
|
649
|
-
act(() => stdin.
|
|
580
|
+
act(() => stdin.write(`\x1b[3~`));
|
|
650
581
|
expect(keyHandler).toHaveBeenCalledTimes(1);
|
|
651
582
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({ name: 'delete' }));
|
|
652
583
|
});
|
|
@@ -672,37 +603,15 @@ describe('Drag and Drop Handling', () => {
|
|
|
672
603
|
it('should start collecting when single quote arrives and not broadcast immediately', async () => {
|
|
673
604
|
const keyHandler = vi.fn();
|
|
674
605
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
675
|
-
act(() =>
|
|
676
|
-
|
|
677
|
-
});
|
|
678
|
-
act(() => {
|
|
679
|
-
stdin.pressKey({
|
|
680
|
-
name: undefined,
|
|
681
|
-
ctrl: false,
|
|
682
|
-
meta: false,
|
|
683
|
-
shift: false,
|
|
684
|
-
paste: false,
|
|
685
|
-
sequence: SINGLE_QUOTE,
|
|
686
|
-
});
|
|
687
|
-
});
|
|
606
|
+
act(() => result.current.subscribe(keyHandler));
|
|
607
|
+
act(() => stdin.write(SINGLE_QUOTE));
|
|
688
608
|
expect(keyHandler).not.toHaveBeenCalled();
|
|
689
609
|
});
|
|
690
610
|
it('should start collecting when double quote arrives and not broadcast immediately', async () => {
|
|
691
611
|
const keyHandler = vi.fn();
|
|
692
612
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
693
|
-
act(() =>
|
|
694
|
-
|
|
695
|
-
});
|
|
696
|
-
act(() => {
|
|
697
|
-
stdin.pressKey({
|
|
698
|
-
name: undefined,
|
|
699
|
-
ctrl: false,
|
|
700
|
-
meta: false,
|
|
701
|
-
shift: false,
|
|
702
|
-
paste: false,
|
|
703
|
-
sequence: DOUBLE_QUOTE,
|
|
704
|
-
});
|
|
705
|
-
});
|
|
613
|
+
act(() => result.current.subscribe(keyHandler));
|
|
614
|
+
act(() => stdin.write(DOUBLE_QUOTE));
|
|
706
615
|
expect(keyHandler).not.toHaveBeenCalled();
|
|
707
616
|
});
|
|
708
617
|
});
|
|
@@ -710,31 +619,11 @@ describe('Drag and Drop Handling', () => {
|
|
|
710
619
|
it('should collect single character inputs during drag mode', async () => {
|
|
711
620
|
const keyHandler = vi.fn();
|
|
712
621
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
713
|
-
act(() =>
|
|
714
|
-
result.current.subscribe(keyHandler);
|
|
715
|
-
});
|
|
622
|
+
act(() => result.current.subscribe(keyHandler));
|
|
716
623
|
// Start by single quote
|
|
717
|
-
act(() =>
|
|
718
|
-
stdin.pressKey({
|
|
719
|
-
name: undefined,
|
|
720
|
-
ctrl: false,
|
|
721
|
-
meta: false,
|
|
722
|
-
shift: false,
|
|
723
|
-
paste: false,
|
|
724
|
-
sequence: SINGLE_QUOTE,
|
|
725
|
-
});
|
|
726
|
-
});
|
|
624
|
+
act(() => stdin.write(SINGLE_QUOTE));
|
|
727
625
|
// Send single character
|
|
728
|
-
act(() =>
|
|
729
|
-
stdin.pressKey({
|
|
730
|
-
name: undefined,
|
|
731
|
-
ctrl: false,
|
|
732
|
-
meta: false,
|
|
733
|
-
shift: false,
|
|
734
|
-
paste: false,
|
|
735
|
-
sequence: 'a',
|
|
736
|
-
});
|
|
737
|
-
});
|
|
626
|
+
act(() => stdin.write('a'));
|
|
738
627
|
// Character should not be immediately broadcast
|
|
739
628
|
expect(keyHandler).not.toHaveBeenCalled();
|
|
740
629
|
// Fast-forward to completion timeout
|
|
@@ -751,61 +640,14 @@ describe('Drag and Drop Handling', () => {
|
|
|
751
640
|
it('should collect multiple characters and complete on timeout', async () => {
|
|
752
641
|
const keyHandler = vi.fn();
|
|
753
642
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
754
|
-
act(() =>
|
|
755
|
-
result.current.subscribe(keyHandler);
|
|
756
|
-
});
|
|
643
|
+
act(() => result.current.subscribe(keyHandler));
|
|
757
644
|
// Start by single quote
|
|
758
|
-
act(() =>
|
|
759
|
-
stdin.pressKey({
|
|
760
|
-
name: undefined,
|
|
761
|
-
ctrl: false,
|
|
762
|
-
meta: false,
|
|
763
|
-
shift: false,
|
|
764
|
-
paste: false,
|
|
765
|
-
sequence: SINGLE_QUOTE,
|
|
766
|
-
});
|
|
767
|
-
});
|
|
645
|
+
act(() => stdin.write(SINGLE_QUOTE));
|
|
768
646
|
// Send multiple characters
|
|
769
|
-
act(() =>
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
meta: false,
|
|
774
|
-
shift: false,
|
|
775
|
-
paste: false,
|
|
776
|
-
sequence: 'p',
|
|
777
|
-
});
|
|
778
|
-
});
|
|
779
|
-
act(() => {
|
|
780
|
-
stdin.pressKey({
|
|
781
|
-
name: undefined,
|
|
782
|
-
ctrl: false,
|
|
783
|
-
meta: false,
|
|
784
|
-
shift: false,
|
|
785
|
-
paste: false,
|
|
786
|
-
sequence: 'a',
|
|
787
|
-
});
|
|
788
|
-
});
|
|
789
|
-
act(() => {
|
|
790
|
-
stdin.pressKey({
|
|
791
|
-
name: undefined,
|
|
792
|
-
ctrl: false,
|
|
793
|
-
meta: false,
|
|
794
|
-
shift: false,
|
|
795
|
-
paste: false,
|
|
796
|
-
sequence: 't',
|
|
797
|
-
});
|
|
798
|
-
});
|
|
799
|
-
act(() => {
|
|
800
|
-
stdin.pressKey({
|
|
801
|
-
name: undefined,
|
|
802
|
-
ctrl: false,
|
|
803
|
-
meta: false,
|
|
804
|
-
shift: false,
|
|
805
|
-
paste: false,
|
|
806
|
-
sequence: 'h',
|
|
807
|
-
});
|
|
808
|
-
});
|
|
647
|
+
act(() => stdin.write('p'));
|
|
648
|
+
act(() => stdin.write('a'));
|
|
649
|
+
act(() => stdin.write('t'));
|
|
650
|
+
act(() => stdin.write('h'));
|
|
809
651
|
// Characters should not be immediately broadcast
|
|
810
652
|
expect(keyHandler).not.toHaveBeenCalled();
|
|
811
653
|
// Fast-forward to completion timeout
|
|
@@ -839,20 +681,19 @@ describe('Kitty Sequence Parsing', () => {
|
|
|
839
681
|
});
|
|
840
682
|
// Terminals to test
|
|
841
683
|
const terminals = ['iTerm2', 'Ghostty', 'MacTerminal', 'VSCodeTerminal'];
|
|
842
|
-
// Key mappings: letter -> [keycode, accented character
|
|
843
|
-
// Note: µ (mu) is sent with meta:false on iTerm2/VSCode
|
|
684
|
+
// Key mappings: letter -> [keycode, accented character]
|
|
844
685
|
const keys = {
|
|
845
|
-
a: [97, 'å'
|
|
846
|
-
o: [111, 'ø'
|
|
847
|
-
m: [109, 'µ'
|
|
686
|
+
a: [97, 'å'],
|
|
687
|
+
o: [111, 'ø'],
|
|
688
|
+
m: [109, 'µ'],
|
|
848
689
|
};
|
|
849
|
-
it.each(terminals.flatMap((terminal) => Object.entries(keys).map(([key, [keycode, accentedChar
|
|
690
|
+
it.each(terminals.flatMap((terminal) => Object.entries(keys).map(([key, [keycode, accentedChar]]) => {
|
|
850
691
|
if (terminal === 'Ghostty') {
|
|
851
692
|
// Ghostty uses kitty protocol sequences
|
|
852
693
|
return {
|
|
853
694
|
terminal,
|
|
854
695
|
key,
|
|
855
|
-
|
|
696
|
+
chunk: `\x1b[${keycode};3u`,
|
|
856
697
|
expected: {
|
|
857
698
|
name: key,
|
|
858
699
|
ctrl: false,
|
|
@@ -869,14 +710,7 @@ describe('Kitty Sequence Parsing', () => {
|
|
|
869
710
|
terminal,
|
|
870
711
|
key,
|
|
871
712
|
kitty: false,
|
|
872
|
-
|
|
873
|
-
sequence: `\x1b${key}`,
|
|
874
|
-
name: key,
|
|
875
|
-
ctrl: false,
|
|
876
|
-
meta: true,
|
|
877
|
-
shift: false,
|
|
878
|
-
paste: false,
|
|
879
|
-
},
|
|
713
|
+
chunk: `\x1b${key}`,
|
|
880
714
|
expected: {
|
|
881
715
|
sequence: `\x1b${key}`,
|
|
882
716
|
name: key,
|
|
@@ -889,18 +723,12 @@ describe('Kitty Sequence Parsing', () => {
|
|
|
889
723
|
}
|
|
890
724
|
else {
|
|
891
725
|
// iTerm2 and VSCode send accented characters (å, ø, µ)
|
|
892
|
-
// Note: µ
|
|
726
|
+
// Note: µ (mu) is sent with meta:false on iTerm2/VSCode but
|
|
727
|
+
// gets converted to m with meta:true
|
|
893
728
|
return {
|
|
894
729
|
terminal,
|
|
895
730
|
key,
|
|
896
|
-
|
|
897
|
-
name: key,
|
|
898
|
-
ctrl: false,
|
|
899
|
-
meta: shouldHaveMeta,
|
|
900
|
-
shift: false,
|
|
901
|
-
paste: false,
|
|
902
|
-
sequence: accentedChar,
|
|
903
|
-
},
|
|
731
|
+
chunk: accentedChar,
|
|
904
732
|
expected: {
|
|
905
733
|
name: key,
|
|
906
734
|
ctrl: false,
|
|
@@ -911,19 +739,14 @@ describe('Kitty Sequence Parsing', () => {
|
|
|
911
739
|
},
|
|
912
740
|
};
|
|
913
741
|
}
|
|
914
|
-
})))('should handle Alt+$key in $terminal', ({
|
|
742
|
+
})))('should handle Alt+$key in $terminal', ({ chunk, expected, kitty = true, }) => {
|
|
915
743
|
const keyHandler = vi.fn();
|
|
916
744
|
const testWrapper = ({ children }) => (_jsx(KeypressProvider, { kittyProtocolEnabled: kitty, children: children }));
|
|
917
745
|
const { result } = renderHook(() => useKeypressContext(), {
|
|
918
746
|
wrapper: testWrapper,
|
|
919
747
|
});
|
|
920
748
|
act(() => result.current.subscribe(keyHandler));
|
|
921
|
-
|
|
922
|
-
act(() => stdin.sendKittySequence(kittySequence));
|
|
923
|
-
}
|
|
924
|
-
else if (input) {
|
|
925
|
-
act(() => stdin.pressKey(input));
|
|
926
|
-
}
|
|
749
|
+
act(() => stdin.write(chunk));
|
|
927
750
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining(expected));
|
|
928
751
|
});
|
|
929
752
|
describe('Backslash key handling', () => {
|
|
@@ -937,14 +760,7 @@ describe('Kitty Sequence Parsing', () => {
|
|
|
937
760
|
const keyHandler = vi.fn();
|
|
938
761
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
939
762
|
act(() => result.current.subscribe(keyHandler));
|
|
940
|
-
act(() => stdin.
|
|
941
|
-
name: undefined,
|
|
942
|
-
ctrl: false,
|
|
943
|
-
meta: false,
|
|
944
|
-
shift: false,
|
|
945
|
-
paste: false,
|
|
946
|
-
sequence: '\\',
|
|
947
|
-
}));
|
|
763
|
+
act(() => stdin.write('\\'));
|
|
948
764
|
// Advance timers to trigger the backslash timeout
|
|
949
765
|
act(() => {
|
|
950
766
|
vi.runAllTimers();
|
|
@@ -958,81 +774,43 @@ describe('Kitty Sequence Parsing', () => {
|
|
|
958
774
|
it('should timeout and flush incomplete kitty sequences after 50ms', async () => {
|
|
959
775
|
const keyHandler = vi.fn();
|
|
960
776
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
961
|
-
act(() =>
|
|
962
|
-
|
|
963
|
-
});
|
|
964
|
-
// Send incomplete kitty sequence
|
|
965
|
-
act(() => {
|
|
966
|
-
stdin.pressKey({
|
|
967
|
-
name: undefined,
|
|
968
|
-
ctrl: false,
|
|
969
|
-
meta: false,
|
|
970
|
-
shift: false,
|
|
971
|
-
paste: false,
|
|
972
|
-
sequence: '\x1b[1;',
|
|
973
|
-
});
|
|
974
|
-
});
|
|
777
|
+
act(() => result.current.subscribe(keyHandler));
|
|
778
|
+
act(() => stdin.write(INCOMPLETE_KITTY_SEQUENCE));
|
|
975
779
|
// Should not broadcast immediately
|
|
976
780
|
expect(keyHandler).not.toHaveBeenCalled();
|
|
977
781
|
// Advance time just before timeout
|
|
978
|
-
act(() =>
|
|
979
|
-
vi.advanceTimersByTime(KITTY_SEQUENCE_TIMEOUT_MS - 5);
|
|
980
|
-
});
|
|
782
|
+
act(() => vi.advanceTimersByTime(KITTY_SEQUENCE_TIMEOUT_MS - 5));
|
|
981
783
|
// Still shouldn't broadcast
|
|
982
784
|
expect(keyHandler).not.toHaveBeenCalled();
|
|
983
785
|
// Advance past timeout
|
|
984
|
-
act(() =>
|
|
985
|
-
vi.advanceTimersByTime(10);
|
|
986
|
-
});
|
|
786
|
+
act(() => vi.advanceTimersByTime(10));
|
|
987
787
|
// Should now broadcast the incomplete sequence as regular input
|
|
988
788
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
989
789
|
name: '',
|
|
990
|
-
sequence:
|
|
790
|
+
sequence: INCOMPLETE_KITTY_SEQUENCE,
|
|
991
791
|
paste: false,
|
|
992
792
|
}));
|
|
993
793
|
});
|
|
994
794
|
it('should immediately flush non-kitty CSI sequences', async () => {
|
|
995
795
|
const keyHandler = vi.fn();
|
|
996
796
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
997
|
-
act(() =>
|
|
998
|
-
result.current.subscribe(keyHandler);
|
|
999
|
-
});
|
|
797
|
+
act(() => result.current.subscribe(keyHandler));
|
|
1000
798
|
// Send a CSI sequence that doesn't match kitty patterns
|
|
1001
799
|
// ESC[m is SGR reset, not a kitty sequence
|
|
1002
|
-
act(() =>
|
|
1003
|
-
stdin.pressKey({
|
|
1004
|
-
name: undefined,
|
|
1005
|
-
ctrl: false,
|
|
1006
|
-
meta: false,
|
|
1007
|
-
shift: false,
|
|
1008
|
-
paste: false,
|
|
1009
|
-
sequence: '\x1b[m',
|
|
1010
|
-
});
|
|
1011
|
-
});
|
|
800
|
+
act(() => stdin.write('\x1b[m'));
|
|
1012
801
|
// Should broadcast immediately as it's not a valid kitty pattern
|
|
1013
802
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
1014
|
-
name: undefined,
|
|
1015
|
-
|
|
803
|
+
name: 'undefined',
|
|
804
|
+
code: '[m',
|
|
1016
805
|
paste: false,
|
|
1017
806
|
}));
|
|
1018
807
|
});
|
|
1019
808
|
it('should parse valid kitty sequences immediately when complete', async () => {
|
|
1020
809
|
const keyHandler = vi.fn();
|
|
1021
810
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
1022
|
-
act(() =>
|
|
1023
|
-
result.current.subscribe(keyHandler);
|
|
1024
|
-
});
|
|
811
|
+
act(() => result.current.subscribe(keyHandler));
|
|
1025
812
|
// Send complete kitty sequence for Ctrl+A
|
|
1026
|
-
act(() =>
|
|
1027
|
-
stdin.pressKey({
|
|
1028
|
-
name: undefined,
|
|
1029
|
-
ctrl: false,
|
|
1030
|
-
meta: false,
|
|
1031
|
-
shift: false,
|
|
1032
|
-
paste: false,
|
|
1033
|
-
sequence: '\x1b[97;5u',
|
|
1034
|
-
});
|
|
1035
|
-
});
|
|
813
|
+
act(() => stdin.write('\x1b[97;5u'));
|
|
1036
814
|
// Should parse and broadcast immediately
|
|
1037
815
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
1038
816
|
name: 'a',
|
|
@@ -1043,20 +821,9 @@ describe('Kitty Sequence Parsing', () => {
|
|
|
1043
821
|
it('should handle batched kitty sequences correctly', async () => {
|
|
1044
822
|
const keyHandler = vi.fn();
|
|
1045
823
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
1046
|
-
act(() =>
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
// Send multiple kitty sequences at once
|
|
1050
|
-
act(() => {
|
|
1051
|
-
stdin.pressKey({
|
|
1052
|
-
name: undefined,
|
|
1053
|
-
ctrl: false,
|
|
1054
|
-
meta: false,
|
|
1055
|
-
shift: false,
|
|
1056
|
-
paste: false,
|
|
1057
|
-
sequence: '\x1b[97;5u\x1b[98;5u', // Ctrl+a followed by Ctrl+b
|
|
1058
|
-
});
|
|
1059
|
-
});
|
|
824
|
+
act(() => result.current.subscribe(keyHandler));
|
|
825
|
+
// Send Ctrl+a followed by Ctrl+b
|
|
826
|
+
act(() => stdin.write('\x1b[97;5u\x1b[98;5u'));
|
|
1060
827
|
// Should parse both sequences
|
|
1061
828
|
expect(keyHandler).toHaveBeenCalledTimes(2);
|
|
1062
829
|
expect(keyHandler).toHaveBeenNthCalledWith(1, expect.objectContaining({
|
|
@@ -1073,35 +840,12 @@ describe('Kitty Sequence Parsing', () => {
|
|
|
1073
840
|
it('should clear kitty buffer and timeout on Ctrl+C', async () => {
|
|
1074
841
|
const keyHandler = vi.fn();
|
|
1075
842
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
1076
|
-
act(() =>
|
|
1077
|
-
|
|
1078
|
-
});
|
|
1079
|
-
// Send incomplete kitty sequence
|
|
1080
|
-
act(() => {
|
|
1081
|
-
stdin.pressKey({
|
|
1082
|
-
name: undefined,
|
|
1083
|
-
ctrl: false,
|
|
1084
|
-
meta: false,
|
|
1085
|
-
shift: false,
|
|
1086
|
-
paste: false,
|
|
1087
|
-
sequence: '\x1b[1;',
|
|
1088
|
-
});
|
|
1089
|
-
});
|
|
843
|
+
act(() => result.current.subscribe(keyHandler));
|
|
844
|
+
act(() => stdin.write(INCOMPLETE_KITTY_SEQUENCE));
|
|
1090
845
|
// Press Ctrl+C
|
|
1091
|
-
act(() =>
|
|
1092
|
-
stdin.pressKey({
|
|
1093
|
-
name: 'c',
|
|
1094
|
-
ctrl: true,
|
|
1095
|
-
meta: false,
|
|
1096
|
-
shift: false,
|
|
1097
|
-
paste: false,
|
|
1098
|
-
sequence: '\x03',
|
|
1099
|
-
});
|
|
1100
|
-
});
|
|
846
|
+
act(() => stdin.write('\x03'));
|
|
1101
847
|
// Advance past timeout
|
|
1102
|
-
act(() =>
|
|
1103
|
-
vi.advanceTimersByTime(KITTY_SEQUENCE_TIMEOUT_MS + 10);
|
|
1104
|
-
});
|
|
848
|
+
act(() => vi.advanceTimersByTime(KITTY_SEQUENCE_TIMEOUT_MS + 10));
|
|
1105
849
|
// Should only have received Ctrl+C, not the incomplete sequence
|
|
1106
850
|
expect(keyHandler).toHaveBeenCalledTimes(1);
|
|
1107
851
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
@@ -1112,20 +856,10 @@ describe('Kitty Sequence Parsing', () => {
|
|
|
1112
856
|
it('should handle mixed valid and invalid sequences', async () => {
|
|
1113
857
|
const keyHandler = vi.fn();
|
|
1114
858
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
1115
|
-
act(() =>
|
|
1116
|
-
result.current.subscribe(keyHandler);
|
|
1117
|
-
});
|
|
859
|
+
act(() => result.current.subscribe(keyHandler));
|
|
1118
860
|
// Send valid kitty sequence followed by invalid CSI
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
name: undefined,
|
|
1122
|
-
ctrl: false,
|
|
1123
|
-
meta: false,
|
|
1124
|
-
shift: false,
|
|
1125
|
-
paste: false,
|
|
1126
|
-
sequence: '\x1b[13u\x1b[!', // Valid enter, then invalid sequence
|
|
1127
|
-
});
|
|
1128
|
-
});
|
|
861
|
+
// Valid enter, then invalid sequence
|
|
862
|
+
act(() => stdin.write('\x1b[13u\x1b[!'));
|
|
1129
863
|
// Should parse valid sequence and flush invalid immediately
|
|
1130
864
|
expect(keyHandler).toHaveBeenCalledTimes(2);
|
|
1131
865
|
expect(keyHandler).toHaveBeenNthCalledWith(1, expect.objectContaining({
|
|
@@ -1133,8 +867,8 @@ describe('Kitty Sequence Parsing', () => {
|
|
|
1133
867
|
kittyProtocol: true,
|
|
1134
868
|
}));
|
|
1135
869
|
expect(keyHandler).toHaveBeenNthCalledWith(2, expect.objectContaining({
|
|
1136
|
-
name: '',
|
|
1137
|
-
|
|
870
|
+
name: 'undefined',
|
|
871
|
+
code: '[!',
|
|
1138
872
|
}));
|
|
1139
873
|
});
|
|
1140
874
|
it('should not buffer sequences when kitty protocol is disabled', async () => {
|
|
@@ -1142,20 +876,9 @@ describe('Kitty Sequence Parsing', () => {
|
|
|
1142
876
|
const { result } = renderHook(() => useKeypressContext(), {
|
|
1143
877
|
wrapper: ({ children }) => wrapper({ children, kittyProtocolEnabled: false }),
|
|
1144
878
|
});
|
|
1145
|
-
act(() =>
|
|
1146
|
-
result.current.subscribe(keyHandler);
|
|
1147
|
-
});
|
|
879
|
+
act(() => result.current.subscribe(keyHandler));
|
|
1148
880
|
// Send what would be a kitty sequence
|
|
1149
|
-
act(() =>
|
|
1150
|
-
stdin.pressKey({
|
|
1151
|
-
name: undefined,
|
|
1152
|
-
ctrl: false,
|
|
1153
|
-
meta: false,
|
|
1154
|
-
shift: false,
|
|
1155
|
-
paste: false,
|
|
1156
|
-
sequence: '\x1b[13u',
|
|
1157
|
-
});
|
|
1158
|
-
});
|
|
881
|
+
act(() => stdin.write('\x1b[13u'));
|
|
1159
882
|
// Should pass through without parsing
|
|
1160
883
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
1161
884
|
sequence: '\x1b[13u',
|
|
@@ -1191,113 +914,56 @@ describe('Kitty Sequence Parsing', () => {
|
|
|
1191
914
|
it('should reset timeout when new input arrives', async () => {
|
|
1192
915
|
const keyHandler = vi.fn();
|
|
1193
916
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
1194
|
-
act(() =>
|
|
1195
|
-
result.current.subscribe(keyHandler);
|
|
1196
|
-
});
|
|
917
|
+
act(() => result.current.subscribe(keyHandler));
|
|
1197
918
|
// Start incomplete sequence
|
|
1198
|
-
act(() =>
|
|
1199
|
-
stdin.pressKey({
|
|
1200
|
-
name: undefined,
|
|
1201
|
-
ctrl: false,
|
|
1202
|
-
meta: false,
|
|
1203
|
-
shift: false,
|
|
1204
|
-
paste: false,
|
|
1205
|
-
sequence: '\x1b[1',
|
|
1206
|
-
});
|
|
1207
|
-
});
|
|
919
|
+
act(() => stdin.write('\x1b[97;13'));
|
|
1208
920
|
// Advance time partway
|
|
1209
|
-
act(() =>
|
|
1210
|
-
vi.advanceTimersByTime(30);
|
|
1211
|
-
});
|
|
921
|
+
act(() => vi.advanceTimersByTime(30));
|
|
1212
922
|
// Add more to sequence
|
|
1213
|
-
act(() =>
|
|
1214
|
-
stdin.pressKey({
|
|
1215
|
-
name: undefined,
|
|
1216
|
-
ctrl: false,
|
|
1217
|
-
meta: false,
|
|
1218
|
-
shift: false,
|
|
1219
|
-
paste: false,
|
|
1220
|
-
sequence: '3',
|
|
1221
|
-
});
|
|
1222
|
-
});
|
|
923
|
+
act(() => stdin.write('5'));
|
|
1223
924
|
// Advance time from the first timeout point
|
|
1224
|
-
act(() =>
|
|
1225
|
-
vi.advanceTimersByTime(25);
|
|
1226
|
-
});
|
|
925
|
+
act(() => vi.advanceTimersByTime(25));
|
|
1227
926
|
// Should not have timed out yet (timeout restarted)
|
|
1228
927
|
expect(keyHandler).not.toHaveBeenCalled();
|
|
1229
928
|
// Complete the sequence
|
|
1230
|
-
act(() =>
|
|
1231
|
-
stdin.pressKey({
|
|
1232
|
-
name: undefined,
|
|
1233
|
-
ctrl: false,
|
|
1234
|
-
meta: false,
|
|
1235
|
-
shift: false,
|
|
1236
|
-
paste: false,
|
|
1237
|
-
sequence: 'u',
|
|
1238
|
-
});
|
|
1239
|
-
});
|
|
929
|
+
act(() => stdin.write('u'));
|
|
1240
930
|
// Should now parse as complete enter key
|
|
1241
931
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
1242
|
-
name: '
|
|
932
|
+
name: 'a',
|
|
1243
933
|
kittyProtocol: true,
|
|
1244
934
|
}));
|
|
1245
935
|
});
|
|
1246
936
|
it('should flush incomplete kitty sequence on FOCUS_IN event', async () => {
|
|
1247
937
|
const keyHandler = vi.fn();
|
|
1248
938
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
1249
|
-
act(() =>
|
|
1250
|
-
|
|
1251
|
-
});
|
|
1252
|
-
// Send incomplete kitty sequence
|
|
1253
|
-
act(() => {
|
|
1254
|
-
stdin.pressKey({
|
|
1255
|
-
sequence: '\x1b[1;',
|
|
1256
|
-
});
|
|
1257
|
-
});
|
|
939
|
+
act(() => result.current.subscribe(keyHandler));
|
|
940
|
+
act(() => stdin.write(INCOMPLETE_KITTY_SEQUENCE));
|
|
1258
941
|
// Incomplete sequence should be buffered, not broadcast
|
|
1259
942
|
expect(keyHandler).not.toHaveBeenCalled();
|
|
1260
943
|
// Send FOCUS_IN event
|
|
1261
|
-
|
|
1262
|
-
act(() => {
|
|
1263
|
-
stdin.pressKey({
|
|
1264
|
-
sequence: FOCUS_IN,
|
|
1265
|
-
});
|
|
1266
|
-
});
|
|
944
|
+
act(() => stdin.write('\x1b[I'));
|
|
1267
945
|
// The buffered sequence should be flushed
|
|
1268
946
|
expect(keyHandler).toHaveBeenCalledTimes(1);
|
|
1269
947
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
1270
948
|
name: '',
|
|
1271
|
-
sequence:
|
|
949
|
+
sequence: INCOMPLETE_KITTY_SEQUENCE,
|
|
1272
950
|
paste: false,
|
|
1273
951
|
}));
|
|
1274
952
|
});
|
|
1275
953
|
it('should flush incomplete kitty sequence on FOCUS_OUT event', async () => {
|
|
1276
954
|
const keyHandler = vi.fn();
|
|
1277
955
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
1278
|
-
act(() =>
|
|
1279
|
-
|
|
1280
|
-
});
|
|
1281
|
-
// Send incomplete kitty sequence
|
|
1282
|
-
act(() => {
|
|
1283
|
-
stdin.pressKey({
|
|
1284
|
-
sequence: '\x1b[1;',
|
|
1285
|
-
});
|
|
1286
|
-
});
|
|
956
|
+
act(() => result.current.subscribe(keyHandler));
|
|
957
|
+
act(() => stdin.write(INCOMPLETE_KITTY_SEQUENCE));
|
|
1287
958
|
// Incomplete sequence should be buffered, not broadcast
|
|
1288
959
|
expect(keyHandler).not.toHaveBeenCalled();
|
|
1289
960
|
// Send FOCUS_OUT event
|
|
1290
|
-
|
|
1291
|
-
act(() => {
|
|
1292
|
-
stdin.pressKey({
|
|
1293
|
-
sequence: FOCUS_OUT,
|
|
1294
|
-
});
|
|
1295
|
-
});
|
|
961
|
+
act(() => stdin.write('\x1b[O'));
|
|
1296
962
|
// The buffered sequence should be flushed
|
|
1297
963
|
expect(keyHandler).toHaveBeenCalledTimes(1);
|
|
1298
964
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
1299
965
|
name: '',
|
|
1300
|
-
sequence:
|
|
966
|
+
sequence: INCOMPLETE_KITTY_SEQUENCE,
|
|
1301
967
|
paste: false,
|
|
1302
968
|
}));
|
|
1303
969
|
});
|
|
@@ -1305,39 +971,27 @@ describe('Kitty Sequence Parsing', () => {
|
|
|
1305
971
|
vi.useFakeTimers();
|
|
1306
972
|
const keyHandler = vi.fn();
|
|
1307
973
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
1308
|
-
act(() =>
|
|
1309
|
-
|
|
1310
|
-
});
|
|
1311
|
-
// Send incomplete kitty sequence
|
|
1312
|
-
act(() => {
|
|
1313
|
-
stdin.pressKey({
|
|
1314
|
-
sequence: '\x1b[1;',
|
|
1315
|
-
});
|
|
1316
|
-
});
|
|
974
|
+
act(() => result.current.subscribe(keyHandler));
|
|
975
|
+
act(() => stdin.write(INCOMPLETE_KITTY_SEQUENCE));
|
|
1317
976
|
// Incomplete sequence should be buffered, not broadcast
|
|
1318
977
|
expect(keyHandler).not.toHaveBeenCalled();
|
|
1319
978
|
// Send paste start sequence
|
|
1320
|
-
|
|
1321
|
-
act(() => {
|
|
1322
|
-
stdin.emit('data', Buffer.from(PASTE_MODE_PREFIX));
|
|
1323
|
-
});
|
|
979
|
+
act(() => stdin.write(`\x1b[200~`));
|
|
1324
980
|
// The buffered sequence should be flushed
|
|
1325
981
|
expect(keyHandler).toHaveBeenCalledTimes(1);
|
|
1326
982
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
1327
983
|
name: '',
|
|
1328
|
-
sequence:
|
|
984
|
+
sequence: INCOMPLETE_KITTY_SEQUENCE,
|
|
1329
985
|
paste: false,
|
|
1330
986
|
}));
|
|
1331
987
|
// Now send some paste content and end paste to make sure paste still works
|
|
1332
988
|
const pastedText = 'hello';
|
|
1333
989
|
const PASTE_MODE_SUFFIX = `\x1b[201~`;
|
|
1334
990
|
act(() => {
|
|
1335
|
-
stdin.
|
|
1336
|
-
stdin.
|
|
1337
|
-
});
|
|
1338
|
-
act(() => {
|
|
1339
|
-
vi.runAllTimers();
|
|
991
|
+
stdin.write(pastedText);
|
|
992
|
+
stdin.write(PASTE_MODE_SUFFIX);
|
|
1340
993
|
});
|
|
994
|
+
act(() => vi.runAllTimers());
|
|
1341
995
|
// The paste event should be broadcast
|
|
1342
996
|
expect(keyHandler).toHaveBeenCalledTimes(2);
|
|
1343
997
|
expect(keyHandler).toHaveBeenNthCalledWith(2, expect.objectContaining({
|