@google/gemini-cli 0.12.0-nightly.20251023.c4c0c0d1 → 0.12.0-nightly.20251027.cb0947c5
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/dist/package.json +3 -3
- package/dist/src/config/config.d.ts +1 -0
- package/dist/src/config/config.js +15 -3
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/config.test.js +23 -15
- package/dist/src/config/config.test.js.map +1 -1
- package/dist/src/config/extension-manager.js +1 -2
- package/dist/src/config/extension-manager.js.map +1 -1
- package/dist/src/config/extension.test.js +27 -4
- package/dist/src/config/extension.test.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 -10
- package/dist/src/config/extensions/extensionEnablement.test.js.map +1 -1
- package/dist/src/config/extensions/github_fetch.d.ts +1 -1
- package/dist/src/config/extensions/github_fetch.js +13 -1
- package/dist/src/config/extensions/github_fetch.js.map +1 -1
- package/dist/src/config/extensions/github_fetch.test.d.ts +6 -0
- package/dist/src/config/extensions/github_fetch.test.js +169 -0
- package/dist/src/config/extensions/github_fetch.test.js.map +1 -0
- package/dist/src/config/extensions/update.test.js +1 -0
- package/dist/src/config/extensions/update.test.js.map +1 -1
- package/dist/src/config/settings.test.js +2 -2
- package/dist/src/core/initializer.js +2 -1
- package/dist/src/core/initializer.js.map +1 -1
- package/dist/src/gemini.test.js +26 -2
- package/dist/src/gemini.test.js.map +1 -1
- package/dist/src/generated/git-commit.d.ts +2 -2
- package/dist/src/generated/git-commit.js +2 -2
- package/dist/src/nonInteractiveCli.js +16 -4
- package/dist/src/nonInteractiveCli.js.map +1 -1
- package/dist/src/nonInteractiveCli.test.js +67 -12
- package/dist/src/nonInteractiveCli.test.js.map +1 -1
- package/dist/src/ui/AppContainer.js +1 -1
- package/dist/src/ui/AppContainer.js.map +1 -1
- package/dist/src/ui/components/FolderTrustDialog.test.js +1 -0
- package/dist/src/ui/components/FolderTrustDialog.test.js.map +1 -1
- package/dist/src/ui/components/InputPrompt.test.js +442 -342
- package/dist/src/ui/components/InputPrompt.test.js.map +1 -1
- package/dist/src/ui/components/ModelDialog.test.js +1 -0
- package/dist/src/ui/components/ModelDialog.test.js.map +1 -1
- package/dist/src/ui/components/PermissionsModifyTrustDialog.test.js +1 -0
- package/dist/src/ui/components/PermissionsModifyTrustDialog.test.js.map +1 -1
- package/dist/src/ui/components/SettingsDialog.test.js +2 -1
- package/dist/src/ui/components/SettingsDialog.test.js.map +1 -1
- package/dist/src/ui/components/ThemeDialog.test.js +1 -2
- package/dist/src/ui/components/ThemeDialog.test.js.map +1 -1
- package/dist/src/ui/components/shared/BaseSelectionList.test.js +1 -0
- package/dist/src/ui/components/shared/BaseSelectionList.test.js.map +1 -1
- package/dist/src/ui/components/shared/text-buffer.test.js +1 -0
- package/dist/src/ui/components/shared/text-buffer.test.js.map +1 -1
- package/dist/src/ui/components/views/ExtensionsList.d.ts +1 -1
- package/dist/src/ui/components/views/ExtensionsList.js +4 -1
- package/dist/src/ui/components/views/ExtensionsList.js.map +1 -1
- package/dist/src/ui/contexts/KeypressContext.d.ts +3 -2
- package/dist/src/ui/contexts/KeypressContext.js +114 -64
- package/dist/src/ui/contexts/KeypressContext.js.map +1 -1
- package/dist/src/ui/contexts/KeypressContext.test.js +162 -479
- package/dist/src/ui/contexts/KeypressContext.test.js.map +1 -1
- package/dist/src/ui/contexts/SessionContext.test.js +1 -0
- package/dist/src/ui/contexts/SessionContext.test.js.map +1 -1
- package/dist/src/ui/hooks/shellCommandProcessor.test.js +18 -2
- package/dist/src/ui/hooks/shellCommandProcessor.test.js.map +1 -1
- package/dist/src/ui/hooks/slashCommandProcessor.test.js +74 -80
- package/dist/src/ui/hooks/slashCommandProcessor.test.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/useCommandCompletion.test.js +79 -78
- package/dist/src/ui/hooks/useCommandCompletion.test.js.map +1 -1
- package/dist/src/ui/hooks/useConsoleMessages.test.js +26 -9
- package/dist/src/ui/hooks/useConsoleMessages.test.js.map +1 -1
- package/dist/src/ui/hooks/useEditorSettings.test.js +40 -34
- package/dist/src/ui/hooks/useEditorSettings.test.js.map +1 -1
- package/dist/src/ui/hooks/useExtensionUpdates.d.ts +1 -1
- package/dist/src/ui/hooks/useExtensionUpdates.js +2 -1
- package/dist/src/ui/hooks/useExtensionUpdates.js.map +1 -1
- package/dist/src/ui/hooks/useExtensionUpdates.test.js +27 -10
- package/dist/src/ui/hooks/useExtensionUpdates.test.js.map +1 -1
- package/dist/src/ui/hooks/useFlickerDetector.test.js +1 -0
- package/dist/src/ui/hooks/useFlickerDetector.test.js.map +1 -1
- package/dist/src/ui/hooks/useFocus.test.js +25 -9
- package/dist/src/ui/hooks/useFocus.test.js.map +1 -1
- package/dist/src/ui/hooks/useFolderTrust.test.js +1 -0
- package/dist/src/ui/hooks/useFolderTrust.test.js.map +1 -1
- package/dist/src/ui/hooks/useGeminiStream.js +49 -14
- package/dist/src/ui/hooks/useGeminiStream.js.map +1 -1
- package/dist/src/ui/hooks/useGeminiStream.test.js +114 -34
- package/dist/src/ui/hooks/useGeminiStream.test.js.map +1 -1
- package/dist/src/ui/hooks/useGitBranchName.test.js +29 -16
- package/dist/src/ui/hooks/useGitBranchName.test.js.map +1 -1
- package/dist/src/ui/hooks/useHistoryManager.test.js +1 -0
- package/dist/src/ui/hooks/useHistoryManager.test.js.map +1 -1
- package/dist/src/ui/hooks/useIdeTrustListener.test.js +24 -7
- package/dist/src/ui/hooks/useIdeTrustListener.test.js.map +1 -1
- package/dist/src/ui/hooks/useInputHistory.test.js +1 -0
- package/dist/src/ui/hooks/useInputHistory.test.js.map +1 -1
- package/dist/src/ui/hooks/useInputHistoryStore.test.js +1 -0
- package/dist/src/ui/hooks/useInputHistoryStore.test.js.map +1 -1
- package/dist/src/ui/hooks/useKeypress.test.js +94 -113
- package/dist/src/ui/hooks/useKeypress.test.js.map +1 -1
- package/dist/src/ui/hooks/useLoadingIndicator.test.js +24 -6
- package/dist/src/ui/hooks/useLoadingIndicator.test.js.map +1 -1
- package/dist/src/ui/hooks/useMemoryMonitor.test.js +10 -5
- package/dist/src/ui/hooks/useMemoryMonitor.test.js.map +1 -1
- package/dist/src/ui/hooks/useMessageQueue.test.js +61 -45
- package/dist/src/ui/hooks/useMessageQueue.test.js.map +1 -1
- package/dist/src/ui/hooks/useModelCommand.test.js +18 -11
- package/dist/src/ui/hooks/useModelCommand.test.js.map +1 -1
- package/dist/src/ui/hooks/usePermissionsModifyTrust.test.js +1 -0
- package/dist/src/ui/hooks/usePermissionsModifyTrust.test.js.map +1 -1
- package/dist/src/ui/hooks/usePhraseCycler.test.js +1 -0
- package/dist/src/ui/hooks/usePhraseCycler.test.js.map +1 -1
- package/dist/src/ui/hooks/usePrivacySettings.test.js +26 -10
- package/dist/src/ui/hooks/usePrivacySettings.test.js.map +1 -1
- package/dist/src/ui/hooks/useQuotaAndFallback.js +13 -14
- package/dist/src/ui/hooks/useQuotaAndFallback.js.map +1 -1
- package/dist/src/ui/hooks/useQuotaAndFallback.test.js +32 -39
- package/dist/src/ui/hooks/useQuotaAndFallback.test.js.map +1 -1
- package/dist/src/ui/hooks/useReactToolScheduler.d.ts +8 -1
- package/dist/src/ui/hooks/useReactToolScheduler.js +37 -26
- package/dist/src/ui/hooks/useReactToolScheduler.js.map +1 -1
- package/dist/src/ui/hooks/useReactToolScheduler.test.js +1 -0
- package/dist/src/ui/hooks/useReactToolScheduler.test.js.map +1 -1
- package/dist/src/ui/hooks/useSelectionList.test.js +193 -132
- package/dist/src/ui/hooks/useSelectionList.test.js.map +1 -1
- package/dist/src/ui/hooks/useShellHistory.test.js +1 -0
- package/dist/src/ui/hooks/useShellHistory.test.js.map +1 -1
- package/dist/src/ui/hooks/useTimer.test.js +43 -14
- package/dist/src/ui/hooks/useTimer.test.js.map +1 -1
- package/dist/src/ui/hooks/useToolScheduler.test.js +122 -39
- package/dist/src/ui/hooks/useToolScheduler.test.js.map +1 -1
- package/dist/src/ui/hooks/vim.test.js +251 -356
- package/dist/src/ui/hooks/vim.test.js.map +1 -1
- package/dist/src/ui/utils/textOutput.d.ts +25 -0
- package/dist/src/ui/utils/textOutput.js +49 -0
- package/dist/src/ui/utils/textOutput.js.map +1 -0
- package/dist/src/ui/utils/textOutput.test.d.ts +6 -0
- package/dist/src/ui/utils/textOutput.test.js +79 -0
- package/dist/src/ui/utils/textOutput.test.js.map +1 -0
- package/dist/src/ui/utils/updateCheck.d.ts +7 -1
- package/dist/src/ui/utils/updateCheck.js +27 -26
- package/dist/src/ui/utils/updateCheck.js.map +1 -1
- package/dist/src/ui/utils/updateCheck.test.js +19 -49
- package/dist/src/ui/utils/updateCheck.test.js.map +1 -1
- package/dist/src/utils/handleAutoUpdate.js +9 -3
- package/dist/src/utils/handleAutoUpdate.js.map +1 -1
- package/dist/src/zed-integration/zedIntegration.js +5 -10
- package/dist/src/zed-integration/zedIntegration.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/dist/google-gemini-cli-0.12.0-nightly.20251022.0542de95.tgz +0 -0
|
@@ -15,29 +15,20 @@ 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
|
setRawMode = vi.fn();
|
|
21
26
|
on = this.addListener;
|
|
22
27
|
removeListener = super.removeListener;
|
|
23
|
-
write = vi.fn();
|
|
24
28
|
resume = vi.fn();
|
|
25
29
|
pause = vi.fn();
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
this.emit('keypress', null, key);
|
|
29
|
-
}
|
|
30
|
-
// Helper to simulate a kitty protocol sequence
|
|
31
|
-
sendKittySequence(sequence) {
|
|
32
|
-
this.emit('data', Buffer.from(sequence));
|
|
33
|
-
}
|
|
34
|
-
// Helper to simulate a paste event
|
|
35
|
-
sendPaste(text) {
|
|
36
|
-
const PASTE_MODE_PREFIX = `\x1b[200~`;
|
|
37
|
-
const PASTE_MODE_SUFFIX = `\x1b[201~`;
|
|
38
|
-
this.emit('data', Buffer.from(PASTE_MODE_PREFIX));
|
|
39
|
-
this.emit('data', Buffer.from(text));
|
|
40
|
-
this.emit('data', Buffer.from(PASTE_MODE_SUFFIX));
|
|
30
|
+
write(text) {
|
|
31
|
+
this.emit('data', text);
|
|
41
32
|
}
|
|
42
33
|
}
|
|
43
34
|
describe('KeypressContext - Kitty Protocol', () => {
|
|
@@ -58,12 +49,10 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|
|
58
49
|
const { result } = renderHook(() => useKeypressContext(), {
|
|
59
50
|
wrapper: ({ children }) => wrapper({ children, kittyProtocolEnabled: true }),
|
|
60
51
|
});
|
|
61
|
-
act(() =>
|
|
62
|
-
result.current.subscribe(keyHandler);
|
|
63
|
-
});
|
|
52
|
+
act(() => result.current.subscribe(keyHandler));
|
|
64
53
|
// Send kitty protocol sequence for regular enter: ESC[13u
|
|
65
54
|
act(() => {
|
|
66
|
-
stdin.
|
|
55
|
+
stdin.write(`\x1b[13u`);
|
|
67
56
|
});
|
|
68
57
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
69
58
|
name: 'return',
|
|
@@ -78,12 +67,10 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|
|
78
67
|
const { result } = renderHook(() => useKeypressContext(), {
|
|
79
68
|
wrapper: ({ children }) => wrapper({ children, kittyProtocolEnabled: true }),
|
|
80
69
|
});
|
|
81
|
-
act(() =>
|
|
82
|
-
result.current.subscribe(keyHandler);
|
|
83
|
-
});
|
|
70
|
+
act(() => result.current.subscribe(keyHandler));
|
|
84
71
|
// Send kitty protocol sequence for numpad enter: ESC[57414u
|
|
85
72
|
act(() => {
|
|
86
|
-
stdin.
|
|
73
|
+
stdin.write(`\x1b[57414u`);
|
|
87
74
|
});
|
|
88
75
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
89
76
|
name: 'return',
|
|
@@ -98,12 +85,10 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|
|
98
85
|
const { result } = renderHook(() => useKeypressContext(), {
|
|
99
86
|
wrapper: ({ children }) => wrapper({ children, kittyProtocolEnabled: true }),
|
|
100
87
|
});
|
|
101
|
-
act(() =>
|
|
102
|
-
result.current.subscribe(keyHandler);
|
|
103
|
-
});
|
|
88
|
+
act(() => result.current.subscribe(keyHandler));
|
|
104
89
|
// Send kitty protocol sequence for numpad enter with Shift (modifier 2): ESC[57414;2u
|
|
105
90
|
act(() => {
|
|
106
|
-
stdin.
|
|
91
|
+
stdin.write(`\x1b[57414;2u`);
|
|
107
92
|
});
|
|
108
93
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
109
94
|
name: 'return',
|
|
@@ -118,13 +103,9 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|
|
118
103
|
const { result } = renderHook(() => useKeypressContext(), {
|
|
119
104
|
wrapper: ({ children }) => wrapper({ children, kittyProtocolEnabled: true }),
|
|
120
105
|
});
|
|
121
|
-
act(() =>
|
|
122
|
-
result.current.subscribe(keyHandler);
|
|
123
|
-
});
|
|
106
|
+
act(() => result.current.subscribe(keyHandler));
|
|
124
107
|
// Send kitty protocol sequence for numpad enter with Ctrl (modifier 5): ESC[57414;5u
|
|
125
|
-
act(() =>
|
|
126
|
-
stdin.sendKittySequence(`\x1b[57414;5u`);
|
|
127
|
-
});
|
|
108
|
+
act(() => stdin.write(`\x1b[57414;5u`));
|
|
128
109
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
129
110
|
name: 'return',
|
|
130
111
|
kittyProtocol: true,
|
|
@@ -138,12 +119,10 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|
|
138
119
|
const { result } = renderHook(() => useKeypressContext(), {
|
|
139
120
|
wrapper: ({ children }) => wrapper({ children, kittyProtocolEnabled: true }),
|
|
140
121
|
});
|
|
141
|
-
act(() =>
|
|
142
|
-
result.current.subscribe(keyHandler);
|
|
143
|
-
});
|
|
122
|
+
act(() => result.current.subscribe(keyHandler));
|
|
144
123
|
// Send kitty protocol sequence for numpad enter with Alt (modifier 3): ESC[57414;3u
|
|
145
124
|
act(() => {
|
|
146
|
-
stdin.
|
|
125
|
+
stdin.write(`\x1b[57414;3u`);
|
|
147
126
|
});
|
|
148
127
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
149
128
|
name: 'return',
|
|
@@ -158,12 +137,10 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|
|
158
137
|
const { result } = renderHook(() => useKeypressContext(), {
|
|
159
138
|
wrapper: ({ children }) => wrapper({ children, kittyProtocolEnabled: false }),
|
|
160
139
|
});
|
|
161
|
-
act(() =>
|
|
162
|
-
result.current.subscribe(keyHandler);
|
|
163
|
-
});
|
|
140
|
+
act(() => result.current.subscribe(keyHandler));
|
|
164
141
|
// Send kitty protocol sequence for numpad enter
|
|
165
142
|
act(() => {
|
|
166
|
-
stdin.
|
|
143
|
+
stdin.write(`\x1b[57414u`);
|
|
167
144
|
});
|
|
168
145
|
// When kitty protocol is disabled, the sequence should be passed through
|
|
169
146
|
// as individual keypresses, not recognized as a single enter key
|
|
@@ -179,12 +156,10 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|
|
179
156
|
const { result } = renderHook(() => useKeypressContext(), {
|
|
180
157
|
wrapper: ({ children }) => wrapper({ children, kittyProtocolEnabled: true }),
|
|
181
158
|
});
|
|
182
|
-
act(() =>
|
|
183
|
-
result.current.subscribe(keyHandler);
|
|
184
|
-
});
|
|
159
|
+
act(() => result.current.subscribe(keyHandler));
|
|
185
160
|
// Send kitty protocol sequence for escape: ESC[27u
|
|
186
161
|
act(() => {
|
|
187
|
-
stdin.
|
|
162
|
+
stdin.write('\x1b[27u');
|
|
188
163
|
});
|
|
189
164
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
190
165
|
name: 'escape',
|
|
@@ -198,7 +173,7 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|
|
198
173
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
199
174
|
act(() => result.current.subscribe(keyHandler));
|
|
200
175
|
act(() => {
|
|
201
|
-
stdin.
|
|
176
|
+
stdin.write(`\x1b[9u`);
|
|
202
177
|
});
|
|
203
178
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
204
179
|
name: 'tab',
|
|
@@ -212,7 +187,7 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|
|
212
187
|
act(() => result.current.subscribe(keyHandler));
|
|
213
188
|
// Modifier 2 is Shift
|
|
214
189
|
act(() => {
|
|
215
|
-
stdin.
|
|
190
|
+
stdin.write(`\x1b[9;2u`);
|
|
216
191
|
});
|
|
217
192
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
218
193
|
name: 'tab',
|
|
@@ -225,7 +200,7 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|
|
225
200
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
226
201
|
act(() => result.current.subscribe(keyHandler));
|
|
227
202
|
act(() => {
|
|
228
|
-
stdin.
|
|
203
|
+
stdin.write(`\x1b[127u`);
|
|
229
204
|
});
|
|
230
205
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
231
206
|
name: 'backspace',
|
|
@@ -239,7 +214,7 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|
|
239
214
|
act(() => result.current.subscribe(keyHandler));
|
|
240
215
|
// Modifier 3 is Alt/Option
|
|
241
216
|
act(() => {
|
|
242
|
-
stdin.
|
|
217
|
+
stdin.write(`\x1b[127;3u`);
|
|
243
218
|
});
|
|
244
219
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
245
220
|
name: 'backspace',
|
|
@@ -253,7 +228,7 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|
|
253
228
|
act(() => result.current.subscribe(keyHandler));
|
|
254
229
|
// Modifier 5 is Ctrl
|
|
255
230
|
act(() => {
|
|
256
|
-
stdin.
|
|
231
|
+
stdin.write(`\x1b[127;5u`);
|
|
257
232
|
});
|
|
258
233
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
259
234
|
name: 'backspace',
|
|
@@ -269,12 +244,12 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|
|
269
244
|
const { result } = renderHook(() => useKeypressContext(), {
|
|
270
245
|
wrapper,
|
|
271
246
|
});
|
|
272
|
-
act(() =>
|
|
273
|
-
result.current.subscribe(keyHandler);
|
|
274
|
-
});
|
|
247
|
+
act(() => result.current.subscribe(keyHandler));
|
|
275
248
|
// Simulate a bracketed paste event
|
|
276
249
|
act(() => {
|
|
277
|
-
stdin.
|
|
250
|
+
stdin.write(PASTE_START);
|
|
251
|
+
stdin.write(pastedText);
|
|
252
|
+
stdin.write(PASTE_END);
|
|
278
253
|
});
|
|
279
254
|
await waitFor(() => {
|
|
280
255
|
// Expect the handler to be called exactly once for the entire paste
|
|
@@ -286,6 +261,46 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|
|
286
261
|
sequence: pastedText,
|
|
287
262
|
}));
|
|
288
263
|
});
|
|
264
|
+
it('should paste start code split over multiple writes', async () => {
|
|
265
|
+
const keyHandler = vi.fn();
|
|
266
|
+
const pastedText = 'pasted content';
|
|
267
|
+
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
268
|
+
act(() => result.current.subscribe(keyHandler));
|
|
269
|
+
act(() => {
|
|
270
|
+
// Split PASTE_START into two parts
|
|
271
|
+
stdin.write(PASTE_START.slice(0, 3));
|
|
272
|
+
stdin.write(PASTE_START.slice(3));
|
|
273
|
+
stdin.write(pastedText);
|
|
274
|
+
stdin.write(PASTE_END);
|
|
275
|
+
});
|
|
276
|
+
await waitFor(() => {
|
|
277
|
+
expect(keyHandler).toHaveBeenCalledTimes(1);
|
|
278
|
+
});
|
|
279
|
+
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
280
|
+
paste: true,
|
|
281
|
+
sequence: pastedText,
|
|
282
|
+
}));
|
|
283
|
+
});
|
|
284
|
+
it('should paste end code split over multiple writes', async () => {
|
|
285
|
+
const keyHandler = vi.fn();
|
|
286
|
+
const pastedText = 'pasted content';
|
|
287
|
+
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
288
|
+
act(() => result.current.subscribe(keyHandler));
|
|
289
|
+
act(() => {
|
|
290
|
+
stdin.write(PASTE_START);
|
|
291
|
+
stdin.write(pastedText);
|
|
292
|
+
// Split PASTE_END into two parts
|
|
293
|
+
stdin.write(PASTE_END.slice(0, 3));
|
|
294
|
+
stdin.write(PASTE_END.slice(3));
|
|
295
|
+
});
|
|
296
|
+
await waitFor(() => {
|
|
297
|
+
expect(keyHandler).toHaveBeenCalledTimes(1);
|
|
298
|
+
});
|
|
299
|
+
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
300
|
+
paste: true,
|
|
301
|
+
sequence: pastedText,
|
|
302
|
+
}));
|
|
303
|
+
});
|
|
289
304
|
});
|
|
290
305
|
describe('debug keystroke logging', () => {
|
|
291
306
|
let consoleLogSpy;
|
|
@@ -302,12 +317,10 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|
|
302
317
|
const keyHandler = vi.fn();
|
|
303
318
|
const wrapper = ({ children }) => (_jsx(KeypressProvider, { kittyProtocolEnabled: true, debugKeystrokeLogging: false, children: children }));
|
|
304
319
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
305
|
-
act(() =>
|
|
306
|
-
result.current.subscribe(keyHandler);
|
|
307
|
-
});
|
|
320
|
+
act(() => result.current.subscribe(keyHandler));
|
|
308
321
|
// Send a kitty sequence
|
|
309
322
|
act(() => {
|
|
310
|
-
stdin.
|
|
323
|
+
stdin.write('\x1b[27u');
|
|
311
324
|
});
|
|
312
325
|
expect(keyHandler).toHaveBeenCalled();
|
|
313
326
|
expect(consoleLogSpy).not.toHaveBeenCalledWith(expect.stringContaining('[DEBUG] Kitty'));
|
|
@@ -316,13 +329,9 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|
|
316
329
|
const keyHandler = vi.fn();
|
|
317
330
|
const wrapper = ({ children }) => (_jsx(KeypressProvider, { kittyProtocolEnabled: true, debugKeystrokeLogging: true, children: children }));
|
|
318
331
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
319
|
-
act(() =>
|
|
320
|
-
result.current.subscribe(keyHandler);
|
|
321
|
-
});
|
|
332
|
+
act(() => result.current.subscribe(keyHandler));
|
|
322
333
|
// Send a complete kitty sequence for escape
|
|
323
|
-
act(() =>
|
|
324
|
-
stdin.sendKittySequence('\x1b[27u');
|
|
325
|
-
});
|
|
334
|
+
act(() => stdin.write('\x1b[27u'));
|
|
326
335
|
expect(consoleLogSpy).toHaveBeenCalledWith('[DEBUG] Kitty buffer accumulating:', expect.stringContaining('"\\u001b[27u"'));
|
|
327
336
|
const parsedCall = consoleLogSpy.mock.calls.find((args) => typeof args[0] === 'string' &&
|
|
328
337
|
args[0].includes('[DEBUG] Kitty sequence parsed successfully'));
|
|
@@ -333,44 +342,21 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|
|
333
342
|
const keyHandler = vi.fn();
|
|
334
343
|
const wrapper = ({ children }) => (_jsx(KeypressProvider, { kittyProtocolEnabled: true, debugKeystrokeLogging: true, children: children }));
|
|
335
344
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
336
|
-
act(() =>
|
|
337
|
-
result.current.subscribe(keyHandler);
|
|
338
|
-
});
|
|
345
|
+
act(() => result.current.subscribe(keyHandler));
|
|
339
346
|
// Send a long sequence starting with a valid kitty prefix to trigger overflow
|
|
340
347
|
const longSequence = '\x1b[1;' + '1'.repeat(100);
|
|
341
|
-
act(() =>
|
|
342
|
-
stdin.sendKittySequence(longSequence);
|
|
343
|
-
});
|
|
348
|
+
act(() => stdin.write(longSequence));
|
|
344
349
|
expect(consoleLogSpy).toHaveBeenCalledWith('[DEBUG] Kitty buffer overflow, clearing:', expect.any(String));
|
|
345
350
|
});
|
|
346
351
|
it('should log kitty buffer clear on Ctrl+C when debugKeystrokeLogging is true', async () => {
|
|
347
352
|
const keyHandler = vi.fn();
|
|
348
353
|
const wrapper = ({ children }) => (_jsx(KeypressProvider, { kittyProtocolEnabled: true, debugKeystrokeLogging: true, children: children }));
|
|
349
354
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
350
|
-
act(() =>
|
|
351
|
-
|
|
352
|
-
});
|
|
353
|
-
// Send incomplete kitty sequence
|
|
354
|
-
act(() => {
|
|
355
|
-
stdin.pressKey({
|
|
356
|
-
name: undefined,
|
|
357
|
-
ctrl: false,
|
|
358
|
-
meta: false,
|
|
359
|
-
shift: false,
|
|
360
|
-
sequence: '\x1b[1',
|
|
361
|
-
});
|
|
362
|
-
});
|
|
355
|
+
act(() => result.current.subscribe(keyHandler));
|
|
356
|
+
act(() => stdin.write(INCOMPLETE_KITTY_SEQUENCE));
|
|
363
357
|
// Send Ctrl+C
|
|
364
|
-
act(() =>
|
|
365
|
-
|
|
366
|
-
name: 'c',
|
|
367
|
-
ctrl: true,
|
|
368
|
-
meta: false,
|
|
369
|
-
shift: false,
|
|
370
|
-
sequence: '\x03',
|
|
371
|
-
});
|
|
372
|
-
});
|
|
373
|
-
expect(consoleLogSpy).toHaveBeenCalledWith('[DEBUG] Kitty buffer cleared on Ctrl+C:', '\x1b[1');
|
|
358
|
+
act(() => stdin.write('\x03'));
|
|
359
|
+
expect(consoleLogSpy).toHaveBeenCalledWith('[DEBUG] Kitty buffer cleared on Ctrl+C:', INCOMPLETE_KITTY_SEQUENCE);
|
|
374
360
|
// Verify Ctrl+C was handled
|
|
375
361
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
376
362
|
name: 'c',
|
|
@@ -381,24 +367,13 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|
|
381
367
|
const keyHandler = vi.fn();
|
|
382
368
|
const wrapper = ({ children }) => (_jsx(KeypressProvider, { kittyProtocolEnabled: true, debugKeystrokeLogging: true, children: children }));
|
|
383
369
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
384
|
-
act(() =>
|
|
385
|
-
result.current.subscribe(keyHandler);
|
|
386
|
-
});
|
|
370
|
+
act(() => result.current.subscribe(keyHandler));
|
|
387
371
|
// Send incomplete kitty sequence
|
|
388
|
-
|
|
389
|
-
act(() => {
|
|
390
|
-
stdin.pressKey({
|
|
391
|
-
name: undefined,
|
|
392
|
-
ctrl: false,
|
|
393
|
-
meta: false,
|
|
394
|
-
shift: false,
|
|
395
|
-
sequence,
|
|
396
|
-
});
|
|
397
|
-
});
|
|
372
|
+
act(() => stdin.write(INCOMPLETE_KITTY_SEQUENCE));
|
|
398
373
|
// Verify debug logging for accumulation
|
|
399
|
-
expect(consoleLogSpy).toHaveBeenCalledWith('[DEBUG] Kitty buffer accumulating:', JSON.stringify(
|
|
374
|
+
expect(consoleLogSpy).toHaveBeenCalledWith('[DEBUG] Kitty buffer accumulating:', JSON.stringify(INCOMPLETE_KITTY_SEQUENCE));
|
|
400
375
|
// Verify warning for char codes
|
|
401
|
-
expect(consoleWarnSpy).toHaveBeenCalledWith('Kitty sequence buffer has content:', JSON.stringify(
|
|
376
|
+
expect(consoleWarnSpy).toHaveBeenCalledWith('Kitty sequence buffer has content:', JSON.stringify(INCOMPLETE_KITTY_SEQUENCE));
|
|
402
377
|
});
|
|
403
378
|
});
|
|
404
379
|
describe('Parameterized functional keys', () => {
|
|
@@ -414,6 +389,9 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|
|
414
389
|
{ sequence: `\x1b[1~`, expected: { name: 'home' } },
|
|
415
390
|
{ sequence: `\x1b[4~`, expected: { name: 'end' } },
|
|
416
391
|
{ sequence: `\x1b[2~`, expected: { name: 'insert' } },
|
|
392
|
+
// Reverse tabs
|
|
393
|
+
{ sequence: `\x1b[Z`, expected: { name: 'tab', shift: true } },
|
|
394
|
+
{ sequence: `\x1b[1;2Z`, expected: { name: 'tab', shift: true } },
|
|
417
395
|
// Legacy Arrows
|
|
418
396
|
{
|
|
419
397
|
sequence: `\x1b[A`,
|
|
@@ -444,29 +422,17 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|
|
444
422
|
const keyHandler = vi.fn();
|
|
445
423
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
446
424
|
act(() => result.current.subscribe(keyHandler));
|
|
447
|
-
act(() => stdin.
|
|
425
|
+
act(() => stdin.write(sequence));
|
|
448
426
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining(expected));
|
|
449
427
|
});
|
|
450
428
|
});
|
|
451
|
-
describe('Shift+Tab forms', () => {
|
|
452
|
-
it.each([
|
|
453
|
-
{ sequence: `\x1b[Z`, description: 'legacy reverse Tab' },
|
|
454
|
-
{ sequence: `\x1b[1;2Z`, description: 'parameterized reverse Tab' },
|
|
455
|
-
])('should recognize $description "$sequence" as Shift+Tab', ({ sequence }) => {
|
|
456
|
-
const keyHandler = vi.fn();
|
|
457
|
-
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
458
|
-
act(() => result.current.subscribe(keyHandler));
|
|
459
|
-
act(() => stdin.sendKittySequence(sequence));
|
|
460
|
-
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({ name: 'tab', shift: true }));
|
|
461
|
-
});
|
|
462
|
-
});
|
|
463
429
|
describe('Double-tap and batching', () => {
|
|
464
430
|
it('should emit two delete events for double-tap CSI[3~', async () => {
|
|
465
431
|
const keyHandler = vi.fn();
|
|
466
432
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
467
433
|
act(() => result.current.subscribe(keyHandler));
|
|
468
|
-
act(() => stdin.
|
|
469
|
-
act(() => stdin.
|
|
434
|
+
act(() => stdin.write(`\x1b[3~`));
|
|
435
|
+
act(() => stdin.write(`\x1b[3~`));
|
|
470
436
|
expect(keyHandler).toHaveBeenNthCalledWith(1, expect.objectContaining({ name: 'delete' }));
|
|
471
437
|
expect(keyHandler).toHaveBeenNthCalledWith(2, expect.objectContaining({ name: 'delete' }));
|
|
472
438
|
});
|
|
@@ -474,7 +440,7 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|
|
474
440
|
const keyHandler = vi.fn();
|
|
475
441
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
476
442
|
act(() => result.current.subscribe(keyHandler));
|
|
477
|
-
act(() => stdin.
|
|
443
|
+
act(() => stdin.write(`\x1b[3~\x1b[5~`));
|
|
478
444
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({ name: 'delete' }));
|
|
479
445
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({ name: 'pageup' }));
|
|
480
446
|
});
|
|
@@ -485,15 +451,9 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|
|
485
451
|
// Incomplete ESC sequence then a complete Delete
|
|
486
452
|
act(() => {
|
|
487
453
|
// Provide an incomplete ESC sequence chunk with a real ESC character
|
|
488
|
-
stdin.
|
|
489
|
-
name: undefined,
|
|
490
|
-
ctrl: false,
|
|
491
|
-
meta: false,
|
|
492
|
-
shift: false,
|
|
493
|
-
sequence: '\x1b[1;',
|
|
494
|
-
});
|
|
454
|
+
stdin.write('\x1b[1;');
|
|
495
455
|
});
|
|
496
|
-
act(() => stdin.
|
|
456
|
+
act(() => stdin.write(`\x1b[3~`));
|
|
497
457
|
expect(keyHandler).toHaveBeenCalledTimes(1);
|
|
498
458
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({ name: 'delete' }));
|
|
499
459
|
});
|
|
@@ -519,37 +479,15 @@ describe('Drag and Drop Handling', () => {
|
|
|
519
479
|
it('should start collecting when single quote arrives and not broadcast immediately', async () => {
|
|
520
480
|
const keyHandler = vi.fn();
|
|
521
481
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
522
|
-
act(() =>
|
|
523
|
-
|
|
524
|
-
});
|
|
525
|
-
act(() => {
|
|
526
|
-
stdin.pressKey({
|
|
527
|
-
name: undefined,
|
|
528
|
-
ctrl: false,
|
|
529
|
-
meta: false,
|
|
530
|
-
shift: false,
|
|
531
|
-
paste: false,
|
|
532
|
-
sequence: SINGLE_QUOTE,
|
|
533
|
-
});
|
|
534
|
-
});
|
|
482
|
+
act(() => result.current.subscribe(keyHandler));
|
|
483
|
+
act(() => stdin.write(SINGLE_QUOTE));
|
|
535
484
|
expect(keyHandler).not.toHaveBeenCalled();
|
|
536
485
|
});
|
|
537
486
|
it('should start collecting when double quote arrives and not broadcast immediately', async () => {
|
|
538
487
|
const keyHandler = vi.fn();
|
|
539
488
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
540
|
-
act(() =>
|
|
541
|
-
|
|
542
|
-
});
|
|
543
|
-
act(() => {
|
|
544
|
-
stdin.pressKey({
|
|
545
|
-
name: undefined,
|
|
546
|
-
ctrl: false,
|
|
547
|
-
meta: false,
|
|
548
|
-
shift: false,
|
|
549
|
-
paste: false,
|
|
550
|
-
sequence: DOUBLE_QUOTE,
|
|
551
|
-
});
|
|
552
|
-
});
|
|
489
|
+
act(() => result.current.subscribe(keyHandler));
|
|
490
|
+
act(() => stdin.write(DOUBLE_QUOTE));
|
|
553
491
|
expect(keyHandler).not.toHaveBeenCalled();
|
|
554
492
|
});
|
|
555
493
|
});
|
|
@@ -557,31 +495,11 @@ describe('Drag and Drop Handling', () => {
|
|
|
557
495
|
it('should collect single character inputs during drag mode', async () => {
|
|
558
496
|
const keyHandler = vi.fn();
|
|
559
497
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
560
|
-
act(() =>
|
|
561
|
-
result.current.subscribe(keyHandler);
|
|
562
|
-
});
|
|
498
|
+
act(() => result.current.subscribe(keyHandler));
|
|
563
499
|
// Start by single quote
|
|
564
|
-
act(() =>
|
|
565
|
-
stdin.pressKey({
|
|
566
|
-
name: undefined,
|
|
567
|
-
ctrl: false,
|
|
568
|
-
meta: false,
|
|
569
|
-
shift: false,
|
|
570
|
-
paste: false,
|
|
571
|
-
sequence: SINGLE_QUOTE,
|
|
572
|
-
});
|
|
573
|
-
});
|
|
500
|
+
act(() => stdin.write(SINGLE_QUOTE));
|
|
574
501
|
// Send single character
|
|
575
|
-
act(() =>
|
|
576
|
-
stdin.pressKey({
|
|
577
|
-
name: undefined,
|
|
578
|
-
ctrl: false,
|
|
579
|
-
meta: false,
|
|
580
|
-
shift: false,
|
|
581
|
-
paste: false,
|
|
582
|
-
sequence: 'a',
|
|
583
|
-
});
|
|
584
|
-
});
|
|
502
|
+
act(() => stdin.write('a'));
|
|
585
503
|
// Character should not be immediately broadcast
|
|
586
504
|
expect(keyHandler).not.toHaveBeenCalled();
|
|
587
505
|
// Fast-forward to completion timeout
|
|
@@ -598,61 +516,14 @@ describe('Drag and Drop Handling', () => {
|
|
|
598
516
|
it('should collect multiple characters and complete on timeout', async () => {
|
|
599
517
|
const keyHandler = vi.fn();
|
|
600
518
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
601
|
-
act(() =>
|
|
602
|
-
result.current.subscribe(keyHandler);
|
|
603
|
-
});
|
|
519
|
+
act(() => result.current.subscribe(keyHandler));
|
|
604
520
|
// Start by single quote
|
|
605
|
-
act(() =>
|
|
606
|
-
stdin.pressKey({
|
|
607
|
-
name: undefined,
|
|
608
|
-
ctrl: false,
|
|
609
|
-
meta: false,
|
|
610
|
-
shift: false,
|
|
611
|
-
paste: false,
|
|
612
|
-
sequence: SINGLE_QUOTE,
|
|
613
|
-
});
|
|
614
|
-
});
|
|
521
|
+
act(() => stdin.write(SINGLE_QUOTE));
|
|
615
522
|
// Send multiple characters
|
|
616
|
-
act(() =>
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
meta: false,
|
|
621
|
-
shift: false,
|
|
622
|
-
paste: false,
|
|
623
|
-
sequence: 'p',
|
|
624
|
-
});
|
|
625
|
-
});
|
|
626
|
-
act(() => {
|
|
627
|
-
stdin.pressKey({
|
|
628
|
-
name: undefined,
|
|
629
|
-
ctrl: false,
|
|
630
|
-
meta: false,
|
|
631
|
-
shift: false,
|
|
632
|
-
paste: false,
|
|
633
|
-
sequence: 'a',
|
|
634
|
-
});
|
|
635
|
-
});
|
|
636
|
-
act(() => {
|
|
637
|
-
stdin.pressKey({
|
|
638
|
-
name: undefined,
|
|
639
|
-
ctrl: false,
|
|
640
|
-
meta: false,
|
|
641
|
-
shift: false,
|
|
642
|
-
paste: false,
|
|
643
|
-
sequence: 't',
|
|
644
|
-
});
|
|
645
|
-
});
|
|
646
|
-
act(() => {
|
|
647
|
-
stdin.pressKey({
|
|
648
|
-
name: undefined,
|
|
649
|
-
ctrl: false,
|
|
650
|
-
meta: false,
|
|
651
|
-
shift: false,
|
|
652
|
-
paste: false,
|
|
653
|
-
sequence: 'h',
|
|
654
|
-
});
|
|
655
|
-
});
|
|
523
|
+
act(() => stdin.write('p'));
|
|
524
|
+
act(() => stdin.write('a'));
|
|
525
|
+
act(() => stdin.write('t'));
|
|
526
|
+
act(() => stdin.write('h'));
|
|
656
527
|
// Characters should not be immediately broadcast
|
|
657
528
|
expect(keyHandler).not.toHaveBeenCalled();
|
|
658
529
|
// Fast-forward to completion timeout
|
|
@@ -686,20 +557,19 @@ describe('Kitty Sequence Parsing', () => {
|
|
|
686
557
|
});
|
|
687
558
|
// Terminals to test
|
|
688
559
|
const terminals = ['iTerm2', 'Ghostty', 'MacTerminal', 'VSCodeTerminal'];
|
|
689
|
-
// Key mappings: letter -> [keycode, accented character
|
|
690
|
-
// Note: µ (mu) is sent with meta:false on iTerm2/VSCode
|
|
560
|
+
// Key mappings: letter -> [keycode, accented character]
|
|
691
561
|
const keys = {
|
|
692
|
-
a: [97, 'å'
|
|
693
|
-
o: [111, 'ø'
|
|
694
|
-
m: [109, 'µ'
|
|
562
|
+
a: [97, 'å'],
|
|
563
|
+
o: [111, 'ø'],
|
|
564
|
+
m: [109, 'µ'],
|
|
695
565
|
};
|
|
696
|
-
it.each(terminals.flatMap((terminal) => Object.entries(keys).map(([key, [keycode, accentedChar
|
|
566
|
+
it.each(terminals.flatMap((terminal) => Object.entries(keys).map(([key, [keycode, accentedChar]]) => {
|
|
697
567
|
if (terminal === 'Ghostty') {
|
|
698
568
|
// Ghostty uses kitty protocol sequences
|
|
699
569
|
return {
|
|
700
570
|
terminal,
|
|
701
571
|
key,
|
|
702
|
-
|
|
572
|
+
chunk: `\x1b[${keycode};3u`,
|
|
703
573
|
expected: {
|
|
704
574
|
name: key,
|
|
705
575
|
ctrl: false,
|
|
@@ -716,14 +586,7 @@ describe('Kitty Sequence Parsing', () => {
|
|
|
716
586
|
terminal,
|
|
717
587
|
key,
|
|
718
588
|
kitty: false,
|
|
719
|
-
|
|
720
|
-
sequence: `\x1b${key}`,
|
|
721
|
-
name: key,
|
|
722
|
-
ctrl: false,
|
|
723
|
-
meta: true,
|
|
724
|
-
shift: false,
|
|
725
|
-
paste: false,
|
|
726
|
-
},
|
|
589
|
+
chunk: `\x1b${key}`,
|
|
727
590
|
expected: {
|
|
728
591
|
sequence: `\x1b${key}`,
|
|
729
592
|
name: key,
|
|
@@ -736,18 +599,12 @@ describe('Kitty Sequence Parsing', () => {
|
|
|
736
599
|
}
|
|
737
600
|
else {
|
|
738
601
|
// iTerm2 and VSCode send accented characters (å, ø, µ)
|
|
739
|
-
// Note: µ
|
|
602
|
+
// Note: µ (mu) is sent with meta:false on iTerm2/VSCode but
|
|
603
|
+
// gets converted to m with meta:true
|
|
740
604
|
return {
|
|
741
605
|
terminal,
|
|
742
606
|
key,
|
|
743
|
-
|
|
744
|
-
name: key,
|
|
745
|
-
ctrl: false,
|
|
746
|
-
meta: shouldHaveMeta,
|
|
747
|
-
shift: false,
|
|
748
|
-
paste: false,
|
|
749
|
-
sequence: accentedChar,
|
|
750
|
-
},
|
|
607
|
+
chunk: accentedChar,
|
|
751
608
|
expected: {
|
|
752
609
|
name: key,
|
|
753
610
|
ctrl: false,
|
|
@@ -758,19 +615,14 @@ describe('Kitty Sequence Parsing', () => {
|
|
|
758
615
|
},
|
|
759
616
|
};
|
|
760
617
|
}
|
|
761
|
-
})))('should handle Alt+$key in $terminal', ({
|
|
618
|
+
})))('should handle Alt+$key in $terminal', ({ chunk, expected, kitty = true, }) => {
|
|
762
619
|
const keyHandler = vi.fn();
|
|
763
620
|
const testWrapper = ({ children }) => (_jsx(KeypressProvider, { kittyProtocolEnabled: kitty, children: children }));
|
|
764
621
|
const { result } = renderHook(() => useKeypressContext(), {
|
|
765
622
|
wrapper: testWrapper,
|
|
766
623
|
});
|
|
767
624
|
act(() => result.current.subscribe(keyHandler));
|
|
768
|
-
|
|
769
|
-
act(() => stdin.sendKittySequence(kittySequence));
|
|
770
|
-
}
|
|
771
|
-
else if (input) {
|
|
772
|
-
act(() => stdin.pressKey(input));
|
|
773
|
-
}
|
|
625
|
+
act(() => stdin.write(chunk));
|
|
774
626
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining(expected));
|
|
775
627
|
});
|
|
776
628
|
describe('Backslash key handling', () => {
|
|
@@ -784,14 +636,7 @@ describe('Kitty Sequence Parsing', () => {
|
|
|
784
636
|
const keyHandler = vi.fn();
|
|
785
637
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
786
638
|
act(() => result.current.subscribe(keyHandler));
|
|
787
|
-
act(() => stdin.
|
|
788
|
-
name: undefined,
|
|
789
|
-
ctrl: false,
|
|
790
|
-
meta: false,
|
|
791
|
-
shift: false,
|
|
792
|
-
paste: false,
|
|
793
|
-
sequence: '\\',
|
|
794
|
-
}));
|
|
639
|
+
act(() => stdin.write('\\'));
|
|
795
640
|
// Advance timers to trigger the backslash timeout
|
|
796
641
|
act(() => {
|
|
797
642
|
vi.runAllTimers();
|
|
@@ -805,57 +650,30 @@ describe('Kitty Sequence Parsing', () => {
|
|
|
805
650
|
it('should timeout and flush incomplete kitty sequences after 50ms', async () => {
|
|
806
651
|
const keyHandler = vi.fn();
|
|
807
652
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
808
|
-
act(() =>
|
|
809
|
-
|
|
810
|
-
});
|
|
811
|
-
// Send incomplete kitty sequence
|
|
812
|
-
act(() => {
|
|
813
|
-
stdin.pressKey({
|
|
814
|
-
name: undefined,
|
|
815
|
-
ctrl: false,
|
|
816
|
-
meta: false,
|
|
817
|
-
shift: false,
|
|
818
|
-
paste: false,
|
|
819
|
-
sequence: '\x1b[1;',
|
|
820
|
-
});
|
|
821
|
-
});
|
|
653
|
+
act(() => result.current.subscribe(keyHandler));
|
|
654
|
+
act(() => stdin.write(INCOMPLETE_KITTY_SEQUENCE));
|
|
822
655
|
// Should not broadcast immediately
|
|
823
656
|
expect(keyHandler).not.toHaveBeenCalled();
|
|
824
657
|
// Advance time just before timeout
|
|
825
|
-
act(() =>
|
|
826
|
-
vi.advanceTimersByTime(KITTY_SEQUENCE_TIMEOUT_MS - 5);
|
|
827
|
-
});
|
|
658
|
+
act(() => vi.advanceTimersByTime(KITTY_SEQUENCE_TIMEOUT_MS - 5));
|
|
828
659
|
// Still shouldn't broadcast
|
|
829
660
|
expect(keyHandler).not.toHaveBeenCalled();
|
|
830
661
|
// Advance past timeout
|
|
831
|
-
act(() =>
|
|
832
|
-
vi.advanceTimersByTime(10);
|
|
833
|
-
});
|
|
662
|
+
act(() => vi.advanceTimersByTime(10));
|
|
834
663
|
// Should now broadcast the incomplete sequence as regular input
|
|
835
664
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
836
665
|
name: '',
|
|
837
|
-
sequence:
|
|
666
|
+
sequence: INCOMPLETE_KITTY_SEQUENCE,
|
|
838
667
|
paste: false,
|
|
839
668
|
}));
|
|
840
669
|
});
|
|
841
670
|
it('should immediately flush non-kitty CSI sequences', async () => {
|
|
842
671
|
const keyHandler = vi.fn();
|
|
843
672
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
844
|
-
act(() =>
|
|
845
|
-
result.current.subscribe(keyHandler);
|
|
846
|
-
});
|
|
673
|
+
act(() => result.current.subscribe(keyHandler));
|
|
847
674
|
// Send a CSI sequence that doesn't match kitty patterns
|
|
848
675
|
// ESC[m is SGR reset, not a kitty sequence
|
|
849
|
-
act(() =>
|
|
850
|
-
stdin.pressKey({
|
|
851
|
-
name: undefined,
|
|
852
|
-
ctrl: false,
|
|
853
|
-
meta: false,
|
|
854
|
-
shift: false,
|
|
855
|
-
paste: false,
|
|
856
|
-
sequence: '\x1b[m',
|
|
857
|
-
});
|
|
858
|
-
});
|
|
676
|
+
act(() => stdin.write('\x1b[m'));
|
|
859
677
|
// Should broadcast immediately as it's not a valid kitty pattern
|
|
860
678
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
861
679
|
name: '',
|
|
@@ -866,20 +684,9 @@ describe('Kitty Sequence Parsing', () => {
|
|
|
866
684
|
it('should parse valid kitty sequences immediately when complete', async () => {
|
|
867
685
|
const keyHandler = vi.fn();
|
|
868
686
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
869
|
-
act(() =>
|
|
870
|
-
result.current.subscribe(keyHandler);
|
|
871
|
-
});
|
|
687
|
+
act(() => result.current.subscribe(keyHandler));
|
|
872
688
|
// Send complete kitty sequence for Ctrl+A
|
|
873
|
-
act(() =>
|
|
874
|
-
stdin.pressKey({
|
|
875
|
-
name: undefined,
|
|
876
|
-
ctrl: false,
|
|
877
|
-
meta: false,
|
|
878
|
-
shift: false,
|
|
879
|
-
paste: false,
|
|
880
|
-
sequence: '\x1b[97;5u',
|
|
881
|
-
});
|
|
882
|
-
});
|
|
689
|
+
act(() => stdin.write('\x1b[97;5u'));
|
|
883
690
|
// Should parse and broadcast immediately
|
|
884
691
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
885
692
|
name: 'a',
|
|
@@ -890,20 +697,9 @@ describe('Kitty Sequence Parsing', () => {
|
|
|
890
697
|
it('should handle batched kitty sequences correctly', async () => {
|
|
891
698
|
const keyHandler = vi.fn();
|
|
892
699
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
893
|
-
act(() =>
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
// Send multiple kitty sequences at once
|
|
897
|
-
act(() => {
|
|
898
|
-
stdin.pressKey({
|
|
899
|
-
name: undefined,
|
|
900
|
-
ctrl: false,
|
|
901
|
-
meta: false,
|
|
902
|
-
shift: false,
|
|
903
|
-
paste: false,
|
|
904
|
-
sequence: '\x1b[97;5u\x1b[98;5u', // Ctrl+a followed by Ctrl+b
|
|
905
|
-
});
|
|
906
|
-
});
|
|
700
|
+
act(() => result.current.subscribe(keyHandler));
|
|
701
|
+
// Send Ctrl+a followed by Ctrl+b
|
|
702
|
+
act(() => stdin.write('\x1b[97;5u\x1b[98;5u'));
|
|
907
703
|
// Should parse both sequences
|
|
908
704
|
expect(keyHandler).toHaveBeenCalledTimes(2);
|
|
909
705
|
expect(keyHandler).toHaveBeenNthCalledWith(1, expect.objectContaining({
|
|
@@ -920,35 +716,12 @@ describe('Kitty Sequence Parsing', () => {
|
|
|
920
716
|
it('should clear kitty buffer and timeout on Ctrl+C', async () => {
|
|
921
717
|
const keyHandler = vi.fn();
|
|
922
718
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
923
|
-
act(() =>
|
|
924
|
-
|
|
925
|
-
});
|
|
926
|
-
// Send incomplete kitty sequence
|
|
927
|
-
act(() => {
|
|
928
|
-
stdin.pressKey({
|
|
929
|
-
name: undefined,
|
|
930
|
-
ctrl: false,
|
|
931
|
-
meta: false,
|
|
932
|
-
shift: false,
|
|
933
|
-
paste: false,
|
|
934
|
-
sequence: '\x1b[1;',
|
|
935
|
-
});
|
|
936
|
-
});
|
|
719
|
+
act(() => result.current.subscribe(keyHandler));
|
|
720
|
+
act(() => stdin.write(INCOMPLETE_KITTY_SEQUENCE));
|
|
937
721
|
// Press Ctrl+C
|
|
938
|
-
act(() =>
|
|
939
|
-
stdin.pressKey({
|
|
940
|
-
name: 'c',
|
|
941
|
-
ctrl: true,
|
|
942
|
-
meta: false,
|
|
943
|
-
shift: false,
|
|
944
|
-
paste: false,
|
|
945
|
-
sequence: '\x03',
|
|
946
|
-
});
|
|
947
|
-
});
|
|
722
|
+
act(() => stdin.write('\x03'));
|
|
948
723
|
// Advance past timeout
|
|
949
|
-
act(() =>
|
|
950
|
-
vi.advanceTimersByTime(KITTY_SEQUENCE_TIMEOUT_MS + 10);
|
|
951
|
-
});
|
|
724
|
+
act(() => vi.advanceTimersByTime(KITTY_SEQUENCE_TIMEOUT_MS + 10));
|
|
952
725
|
// Should only have received Ctrl+C, not the incomplete sequence
|
|
953
726
|
expect(keyHandler).toHaveBeenCalledTimes(1);
|
|
954
727
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
@@ -959,20 +732,10 @@ describe('Kitty Sequence Parsing', () => {
|
|
|
959
732
|
it('should handle mixed valid and invalid sequences', async () => {
|
|
960
733
|
const keyHandler = vi.fn();
|
|
961
734
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
962
|
-
act(() =>
|
|
963
|
-
result.current.subscribe(keyHandler);
|
|
964
|
-
});
|
|
735
|
+
act(() => result.current.subscribe(keyHandler));
|
|
965
736
|
// Send valid kitty sequence followed by invalid CSI
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
name: undefined,
|
|
969
|
-
ctrl: false,
|
|
970
|
-
meta: false,
|
|
971
|
-
shift: false,
|
|
972
|
-
paste: false,
|
|
973
|
-
sequence: '\x1b[13u\x1b[!', // Valid enter, then invalid sequence
|
|
974
|
-
});
|
|
975
|
-
});
|
|
737
|
+
// Valid enter, then invalid sequence
|
|
738
|
+
act(() => stdin.write('\x1b[13u\x1b[!'));
|
|
976
739
|
// Should parse valid sequence and flush invalid immediately
|
|
977
740
|
expect(keyHandler).toHaveBeenCalledTimes(2);
|
|
978
741
|
expect(keyHandler).toHaveBeenNthCalledWith(1, expect.objectContaining({
|
|
@@ -989,20 +752,9 @@ describe('Kitty Sequence Parsing', () => {
|
|
|
989
752
|
const { result } = renderHook(() => useKeypressContext(), {
|
|
990
753
|
wrapper: ({ children }) => wrapper({ children, kittyProtocolEnabled: false }),
|
|
991
754
|
});
|
|
992
|
-
act(() =>
|
|
993
|
-
result.current.subscribe(keyHandler);
|
|
994
|
-
});
|
|
755
|
+
act(() => result.current.subscribe(keyHandler));
|
|
995
756
|
// Send what would be a kitty sequence
|
|
996
|
-
act(() =>
|
|
997
|
-
stdin.pressKey({
|
|
998
|
-
name: undefined,
|
|
999
|
-
ctrl: false,
|
|
1000
|
-
meta: false,
|
|
1001
|
-
shift: false,
|
|
1002
|
-
paste: false,
|
|
1003
|
-
sequence: '\x1b[13u',
|
|
1004
|
-
});
|
|
1005
|
-
});
|
|
757
|
+
act(() => stdin.write('\x1b[13u'));
|
|
1006
758
|
// Should pass through without parsing
|
|
1007
759
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
1008
760
|
sequence: '\x1b[13u',
|
|
@@ -1038,113 +790,56 @@ describe('Kitty Sequence Parsing', () => {
|
|
|
1038
790
|
it('should reset timeout when new input arrives', async () => {
|
|
1039
791
|
const keyHandler = vi.fn();
|
|
1040
792
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
1041
|
-
act(() =>
|
|
1042
|
-
result.current.subscribe(keyHandler);
|
|
1043
|
-
});
|
|
793
|
+
act(() => result.current.subscribe(keyHandler));
|
|
1044
794
|
// Start incomplete sequence
|
|
1045
|
-
act(() =>
|
|
1046
|
-
stdin.pressKey({
|
|
1047
|
-
name: undefined,
|
|
1048
|
-
ctrl: false,
|
|
1049
|
-
meta: false,
|
|
1050
|
-
shift: false,
|
|
1051
|
-
paste: false,
|
|
1052
|
-
sequence: '\x1b[1',
|
|
1053
|
-
});
|
|
1054
|
-
});
|
|
795
|
+
act(() => stdin.write('\x1b[97;13'));
|
|
1055
796
|
// Advance time partway
|
|
1056
|
-
act(() =>
|
|
1057
|
-
vi.advanceTimersByTime(30);
|
|
1058
|
-
});
|
|
797
|
+
act(() => vi.advanceTimersByTime(30));
|
|
1059
798
|
// Add more to sequence
|
|
1060
|
-
act(() =>
|
|
1061
|
-
stdin.pressKey({
|
|
1062
|
-
name: undefined,
|
|
1063
|
-
ctrl: false,
|
|
1064
|
-
meta: false,
|
|
1065
|
-
shift: false,
|
|
1066
|
-
paste: false,
|
|
1067
|
-
sequence: '3',
|
|
1068
|
-
});
|
|
1069
|
-
});
|
|
799
|
+
act(() => stdin.write('5'));
|
|
1070
800
|
// Advance time from the first timeout point
|
|
1071
|
-
act(() =>
|
|
1072
|
-
vi.advanceTimersByTime(25);
|
|
1073
|
-
});
|
|
801
|
+
act(() => vi.advanceTimersByTime(25));
|
|
1074
802
|
// Should not have timed out yet (timeout restarted)
|
|
1075
803
|
expect(keyHandler).not.toHaveBeenCalled();
|
|
1076
804
|
// Complete the sequence
|
|
1077
|
-
act(() =>
|
|
1078
|
-
stdin.pressKey({
|
|
1079
|
-
name: undefined,
|
|
1080
|
-
ctrl: false,
|
|
1081
|
-
meta: false,
|
|
1082
|
-
shift: false,
|
|
1083
|
-
paste: false,
|
|
1084
|
-
sequence: 'u',
|
|
1085
|
-
});
|
|
1086
|
-
});
|
|
805
|
+
act(() => stdin.write('u'));
|
|
1087
806
|
// Should now parse as complete enter key
|
|
1088
807
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
1089
|
-
name: '
|
|
808
|
+
name: 'a',
|
|
1090
809
|
kittyProtocol: true,
|
|
1091
810
|
}));
|
|
1092
811
|
});
|
|
1093
812
|
it('should flush incomplete kitty sequence on FOCUS_IN event', async () => {
|
|
1094
813
|
const keyHandler = vi.fn();
|
|
1095
814
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
1096
|
-
act(() =>
|
|
1097
|
-
|
|
1098
|
-
});
|
|
1099
|
-
// Send incomplete kitty sequence
|
|
1100
|
-
act(() => {
|
|
1101
|
-
stdin.pressKey({
|
|
1102
|
-
sequence: '\x1b[1;',
|
|
1103
|
-
});
|
|
1104
|
-
});
|
|
815
|
+
act(() => result.current.subscribe(keyHandler));
|
|
816
|
+
act(() => stdin.write(INCOMPLETE_KITTY_SEQUENCE));
|
|
1105
817
|
// Incomplete sequence should be buffered, not broadcast
|
|
1106
818
|
expect(keyHandler).not.toHaveBeenCalled();
|
|
1107
819
|
// Send FOCUS_IN event
|
|
1108
|
-
|
|
1109
|
-
act(() => {
|
|
1110
|
-
stdin.pressKey({
|
|
1111
|
-
sequence: FOCUS_IN,
|
|
1112
|
-
});
|
|
1113
|
-
});
|
|
820
|
+
act(() => stdin.write('\x1b[I'));
|
|
1114
821
|
// The buffered sequence should be flushed
|
|
1115
822
|
expect(keyHandler).toHaveBeenCalledTimes(1);
|
|
1116
823
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
1117
824
|
name: '',
|
|
1118
|
-
sequence:
|
|
825
|
+
sequence: INCOMPLETE_KITTY_SEQUENCE,
|
|
1119
826
|
paste: false,
|
|
1120
827
|
}));
|
|
1121
828
|
});
|
|
1122
829
|
it('should flush incomplete kitty sequence on FOCUS_OUT event', async () => {
|
|
1123
830
|
const keyHandler = vi.fn();
|
|
1124
831
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
1125
|
-
act(() =>
|
|
1126
|
-
|
|
1127
|
-
});
|
|
1128
|
-
// Send incomplete kitty sequence
|
|
1129
|
-
act(() => {
|
|
1130
|
-
stdin.pressKey({
|
|
1131
|
-
sequence: '\x1b[1;',
|
|
1132
|
-
});
|
|
1133
|
-
});
|
|
832
|
+
act(() => result.current.subscribe(keyHandler));
|
|
833
|
+
act(() => stdin.write(INCOMPLETE_KITTY_SEQUENCE));
|
|
1134
834
|
// Incomplete sequence should be buffered, not broadcast
|
|
1135
835
|
expect(keyHandler).not.toHaveBeenCalled();
|
|
1136
836
|
// Send FOCUS_OUT event
|
|
1137
|
-
|
|
1138
|
-
act(() => {
|
|
1139
|
-
stdin.pressKey({
|
|
1140
|
-
sequence: FOCUS_OUT,
|
|
1141
|
-
});
|
|
1142
|
-
});
|
|
837
|
+
act(() => stdin.write('\x1b[O'));
|
|
1143
838
|
// The buffered sequence should be flushed
|
|
1144
839
|
expect(keyHandler).toHaveBeenCalledTimes(1);
|
|
1145
840
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
1146
841
|
name: '',
|
|
1147
|
-
sequence:
|
|
842
|
+
sequence: INCOMPLETE_KITTY_SEQUENCE,
|
|
1148
843
|
paste: false,
|
|
1149
844
|
}));
|
|
1150
845
|
});
|
|
@@ -1152,39 +847,27 @@ describe('Kitty Sequence Parsing', () => {
|
|
|
1152
847
|
vi.useFakeTimers();
|
|
1153
848
|
const keyHandler = vi.fn();
|
|
1154
849
|
const { result } = renderHook(() => useKeypressContext(), { wrapper });
|
|
1155
|
-
act(() =>
|
|
1156
|
-
|
|
1157
|
-
});
|
|
1158
|
-
// Send incomplete kitty sequence
|
|
1159
|
-
act(() => {
|
|
1160
|
-
stdin.pressKey({
|
|
1161
|
-
sequence: '\x1b[1;',
|
|
1162
|
-
});
|
|
1163
|
-
});
|
|
850
|
+
act(() => result.current.subscribe(keyHandler));
|
|
851
|
+
act(() => stdin.write(INCOMPLETE_KITTY_SEQUENCE));
|
|
1164
852
|
// Incomplete sequence should be buffered, not broadcast
|
|
1165
853
|
expect(keyHandler).not.toHaveBeenCalled();
|
|
1166
854
|
// Send paste start sequence
|
|
1167
|
-
|
|
1168
|
-
act(() => {
|
|
1169
|
-
stdin.emit('data', Buffer.from(PASTE_MODE_PREFIX));
|
|
1170
|
-
});
|
|
855
|
+
act(() => stdin.write(`\x1b[200~`));
|
|
1171
856
|
// The buffered sequence should be flushed
|
|
1172
857
|
expect(keyHandler).toHaveBeenCalledTimes(1);
|
|
1173
858
|
expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
|
|
1174
859
|
name: '',
|
|
1175
|
-
sequence:
|
|
860
|
+
sequence: INCOMPLETE_KITTY_SEQUENCE,
|
|
1176
861
|
paste: false,
|
|
1177
862
|
}));
|
|
1178
863
|
// Now send some paste content and end paste to make sure paste still works
|
|
1179
864
|
const pastedText = 'hello';
|
|
1180
865
|
const PASTE_MODE_SUFFIX = `\x1b[201~`;
|
|
1181
866
|
act(() => {
|
|
1182
|
-
stdin.
|
|
1183
|
-
stdin.
|
|
1184
|
-
});
|
|
1185
|
-
act(() => {
|
|
1186
|
-
vi.runAllTimers();
|
|
867
|
+
stdin.write(pastedText);
|
|
868
|
+
stdin.write(PASTE_MODE_SUFFIX);
|
|
1187
869
|
});
|
|
870
|
+
act(() => vi.runAllTimers());
|
|
1188
871
|
// The paste event should be broadcast
|
|
1189
872
|
expect(keyHandler).toHaveBeenCalledTimes(2);
|
|
1190
873
|
expect(keyHandler).toHaveBeenNthCalledWith(2, expect.objectContaining({
|