@google/gemini-cli 0.14.0-preview.0 → 0.15.0-nightly.20251110.c0b766ad

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.
Files changed (107) hide show
  1. package/dist/google-gemini-cli-0.15.0-nightly.20251107.b8eeb553.tgz +0 -0
  2. package/dist/package.json +3 -3
  3. package/dist/src/config/config.d.ts +1 -8
  4. package/dist/src/config/config.js +3 -23
  5. package/dist/src/config/config.js.map +1 -1
  6. package/dist/src/config/config.test.js +11 -11
  7. package/dist/src/config/config.test.js.map +1 -1
  8. package/dist/src/config/extension-manager.js +8 -7
  9. package/dist/src/config/extension-manager.js.map +1 -1
  10. package/dist/src/config/extensions/github.d.ts +1 -1
  11. package/dist/src/config/extensions/github.js +8 -2
  12. package/dist/src/config/extensions/github.js.map +1 -1
  13. package/dist/src/config/extensions/github.test.js +8 -10
  14. package/dist/src/config/extensions/github.test.js.map +1 -1
  15. package/dist/src/gemini.js +2 -2
  16. package/dist/src/gemini.js.map +1 -1
  17. package/dist/src/generated/git-commit.d.ts +2 -2
  18. package/dist/src/generated/git-commit.js +2 -2
  19. package/dist/src/generated/git-commit.js.map +1 -1
  20. package/dist/src/services/FileCommandLoader.js +4 -2
  21. package/dist/src/services/FileCommandLoader.js.map +1 -1
  22. package/dist/src/services/FileCommandLoader.test.js +37 -0
  23. package/dist/src/services/FileCommandLoader.test.js.map +1 -1
  24. package/dist/src/test-utils/render.d.ts +1 -2
  25. package/dist/src/test-utils/render.js +2 -2
  26. package/dist/src/test-utils/render.js.map +1 -1
  27. package/dist/src/ui/AppContainer.js +14 -13
  28. package/dist/src/ui/AppContainer.js.map +1 -1
  29. package/dist/src/ui/commands/directoryCommand.js +2 -10
  30. package/dist/src/ui/commands/directoryCommand.js.map +1 -1
  31. package/dist/src/ui/commands/extensionsCommand.js +91 -2
  32. package/dist/src/ui/commands/extensionsCommand.js.map +1 -1
  33. package/dist/src/ui/commands/extensionsCommand.test.js +125 -1
  34. package/dist/src/ui/commands/extensionsCommand.test.js.map +1 -1
  35. package/dist/src/ui/commands/memoryCommand.js +2 -10
  36. package/dist/src/ui/commands/memoryCommand.js.map +1 -1
  37. package/dist/src/ui/commands/memoryCommand.test.js +11 -28
  38. package/dist/src/ui/commands/memoryCommand.test.js.map +1 -1
  39. package/dist/src/ui/commands/types.d.ts +0 -1
  40. package/dist/src/ui/commands/types.js.map +1 -1
  41. package/dist/src/ui/components/HistoryItemDisplay.js +1 -1
  42. package/dist/src/ui/components/HistoryItemDisplay.js.map +1 -1
  43. package/dist/src/ui/components/InputPrompt.test.js +35 -26
  44. package/dist/src/ui/components/InputPrompt.test.js.map +1 -1
  45. package/dist/src/ui/components/SettingsDialog.test.js +4 -4
  46. package/dist/src/ui/components/SettingsDialog.test.js.map +1 -1
  47. package/dist/src/ui/components/ThemeDialog.test.js +3 -3
  48. package/dist/src/ui/components/ThemeDialog.test.js.map +1 -1
  49. package/dist/src/ui/components/messages/InfoMessage.d.ts +2 -0
  50. package/dist/src/ui/components/messages/InfoMessage.js +4 -3
  51. package/dist/src/ui/components/messages/InfoMessage.js.map +1 -1
  52. package/dist/src/ui/components/shared/Scrollable.js +9 -4
  53. package/dist/src/ui/components/shared/Scrollable.js.map +1 -1
  54. package/dist/src/ui/components/shared/Scrollable.test.js +39 -1
  55. package/dist/src/ui/components/shared/Scrollable.test.js.map +1 -1
  56. package/dist/src/ui/components/shared/ScrollableList.test.js +1 -1
  57. package/dist/src/ui/components/shared/ScrollableList.test.js.map +1 -1
  58. package/dist/src/ui/components/shared/VirtualizedList.js +10 -3
  59. package/dist/src/ui/components/shared/VirtualizedList.js.map +1 -1
  60. package/dist/src/ui/components/shared/VirtualizedList.test.js +23 -0
  61. package/dist/src/ui/components/shared/VirtualizedList.test.js.map +1 -1
  62. package/dist/src/ui/contexts/KeypressContext.d.ts +5 -11
  63. package/dist/src/ui/contexts/KeypressContext.js +446 -755
  64. package/dist/src/ui/contexts/KeypressContext.js.map +1 -1
  65. package/dist/src/ui/contexts/KeypressContext.test.js +103 -334
  66. package/dist/src/ui/contexts/KeypressContext.test.js.map +1 -1
  67. package/dist/src/ui/contexts/ScrollProvider.js +25 -4
  68. package/dist/src/ui/contexts/ScrollProvider.js.map +1 -1
  69. package/dist/src/ui/contexts/ScrollProvider.test.d.ts +6 -0
  70. package/dist/src/ui/contexts/ScrollProvider.test.js +173 -0
  71. package/dist/src/ui/contexts/ScrollProvider.test.js.map +1 -0
  72. package/dist/src/ui/hooks/atCommandProcessor.test.js +1 -0
  73. package/dist/src/ui/hooks/atCommandProcessor.test.js.map +1 -1
  74. package/dist/src/ui/hooks/slashCommandProcessor.d.ts +1 -1
  75. package/dist/src/ui/hooks/slashCommandProcessor.js +1 -3
  76. package/dist/src/ui/hooks/slashCommandProcessor.js.map +1 -1
  77. package/dist/src/ui/hooks/slashCommandProcessor.test.js +1 -2
  78. package/dist/src/ui/hooks/slashCommandProcessor.test.js.map +1 -1
  79. package/dist/src/ui/hooks/useBatchedScroll.d.ts +14 -0
  80. package/dist/src/ui/hooks/useBatchedScroll.js +27 -0
  81. package/dist/src/ui/hooks/useBatchedScroll.js.map +1 -0
  82. package/dist/src/ui/hooks/useBatchedScroll.test.d.ts +6 -0
  83. package/dist/src/ui/hooks/useBatchedScroll.test.js +62 -0
  84. package/dist/src/ui/hooks/useBatchedScroll.test.js.map +1 -0
  85. package/dist/src/ui/hooks/useFocus.test.js +10 -10
  86. package/dist/src/ui/hooks/useFocus.test.js.map +1 -1
  87. package/dist/src/ui/hooks/useKeypress.test.js +8 -14
  88. package/dist/src/ui/hooks/useKeypress.test.js.map +1 -1
  89. package/dist/src/ui/noninteractive/nonInteractiveUi.js +0 -1
  90. package/dist/src/ui/noninteractive/nonInteractiveUi.js.map +1 -1
  91. package/dist/src/ui/state/extensions.d.ts +5 -0
  92. package/dist/src/ui/state/extensions.js +12 -0
  93. package/dist/src/ui/state/extensions.js.map +1 -1
  94. package/dist/src/ui/state/extensions.test.d.ts +6 -0
  95. package/dist/src/ui/state/extensions.test.js +62 -0
  96. package/dist/src/ui/state/extensions.test.js.map +1 -0
  97. package/dist/src/ui/types.d.ts +6 -0
  98. package/dist/src/ui/types.js +4 -0
  99. package/dist/src/ui/types.js.map +1 -1
  100. package/dist/src/ui/utils/terminalSetup.d.ts +1 -0
  101. package/dist/src/ui/utils/terminalSetup.js +1 -1
  102. package/dist/src/ui/utils/terminalSetup.js.map +1 -1
  103. package/dist/tsconfig.tsbuildinfo +1 -1
  104. package/package.json +4 -4
  105. package/dist/src/ui/utils/platformConstants.d.ts +0 -75
  106. package/dist/src/ui/utils/platformConstants.js +0 -78
  107. package/dist/src/ui/utils/platformConstants.js.map +0 -1
@@ -2,11 +2,8 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { act } from 'react';
3
3
  import { renderHook } from '../../test-utils/render.js';
4
4
  import { waitFor } from '../../test-utils/async.js';
5
- import { vi } from 'vitest';
6
- import { KeypressProvider, useKeypressContext, DRAG_COMPLETION_TIMEOUT_MS, KITTY_SEQUENCE_TIMEOUT_MS,
7
- // CSI_END_O,
8
- // SS3_END,
9
- SINGLE_QUOTE, DOUBLE_QUOTE, } from './KeypressContext.js';
5
+ import { vi, afterAll, beforeAll } from 'vitest';
6
+ import { KeypressProvider, useKeypressContext, ESC_TIMEOUT, } from './KeypressContext.js';
10
7
  import { useStdin } from 'ink';
11
8
  import { EventEmitter } from 'node:events';
12
9
  // Mock the 'ink' module to control stdin
@@ -34,17 +31,19 @@ class MockStdin extends EventEmitter {
34
31
  }
35
32
  }
36
33
  // Helper function to setup keypress test with standard configuration
37
- const setupKeypressTest = (kittyProtocolEnabled = true) => {
34
+ const setupKeypressTest = () => {
38
35
  const keyHandler = vi.fn();
39
- const wrapper = ({ children }) => (_jsx(KeypressProvider, { kittyProtocolEnabled: kittyProtocolEnabled, children: children }));
36
+ const wrapper = ({ children }) => (_jsx(KeypressProvider, { children: children }));
40
37
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
41
38
  act(() => result.current.subscribe(keyHandler));
42
39
  return { result, keyHandler };
43
40
  };
44
- describe('KeypressContext - Kitty Protocol', () => {
41
+ describe('KeypressContext', () => {
45
42
  let stdin;
46
43
  const mockSetRawMode = vi.fn();
47
- const wrapper = ({ children, kittyProtocolEnabled = true, }) => (_jsx(KeypressProvider, { kittyProtocolEnabled: kittyProtocolEnabled ?? false, children: children }));
44
+ const wrapper = ({ children }) => (_jsx(KeypressProvider, { children: children }));
45
+ beforeAll(() => vi.useFakeTimers());
46
+ afterAll(() => vi.useRealTimers());
48
47
  beforeEach(() => {
49
48
  vi.clearAllMocks();
50
49
  stdin = new MockStdin();
@@ -64,18 +63,25 @@ describe('KeypressContext - Kitty Protocol', () => {
64
63
  sequence: '\x1b[57414u',
65
64
  },
66
65
  ])('should recognize $name in kitty protocol', async ({ sequence }) => {
67
- const { keyHandler } = setupKeypressTest(true);
68
- act(() => {
69
- stdin.write(sequence);
70
- });
66
+ const { keyHandler } = setupKeypressTest();
67
+ act(() => stdin.write(sequence));
71
68
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
72
69
  name: 'return',
73
- kittyProtocol: true,
74
70
  ctrl: false,
75
71
  meta: false,
76
72
  shift: false,
77
73
  }));
78
74
  });
75
+ it('should handle backslash return', async () => {
76
+ const { keyHandler } = setupKeypressTest();
77
+ act(() => stdin.write('\\\r'));
78
+ expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
79
+ name: 'return',
80
+ ctrl: false,
81
+ meta: false,
82
+ shift: true,
83
+ }));
84
+ });
79
85
  it.each([
80
86
  {
81
87
  modifier: 'Shift',
@@ -93,60 +99,57 @@ describe('KeypressContext - Kitty Protocol', () => {
93
99
  expected: { ctrl: false, meta: true, shift: false },
94
100
  },
95
101
  ])('should handle numpad enter with $modifier modifier', async ({ sequence, expected }) => {
96
- const { keyHandler } = setupKeypressTest(true);
102
+ const { keyHandler } = setupKeypressTest();
97
103
  act(() => stdin.write(sequence));
98
104
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
99
105
  name: 'return',
100
- kittyProtocol: true,
101
106
  ...expected,
102
107
  }));
103
108
  });
104
- it('should not process kitty sequences when kitty protocol is disabled', async () => {
105
- const { keyHandler } = setupKeypressTest(false);
106
- // Send kitty protocol sequence for numpad enter
107
- act(() => {
108
- stdin.write(`\x1b[57414u`);
109
- });
110
- // When kitty protocol is disabled, the sequence should be passed through
111
- // as individual keypresses, not recognized as a single enter key
112
- expect(keyHandler).not.toHaveBeenCalledWith(expect.objectContaining({
113
- name: 'return',
114
- kittyProtocol: true,
115
- }));
116
- });
117
109
  });
118
110
  describe('Escape key handling', () => {
119
111
  it('should recognize escape key (keycode 27) in kitty protocol', async () => {
120
- const { keyHandler } = setupKeypressTest(true);
112
+ const { keyHandler } = setupKeypressTest();
121
113
  // Send kitty protocol sequence for escape: ESC[27u
122
114
  act(() => {
123
115
  stdin.write('\x1b[27u');
124
116
  });
125
117
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
126
118
  name: 'escape',
127
- kittyProtocol: true,
128
119
  }));
129
120
  });
121
+ it('should handle double Escape', async () => {
122
+ const keyHandler = vi.fn();
123
+ const wrapper = ({ children }) => (_jsx(KeypressProvider, { children: children }));
124
+ const { result } = renderHook(() => useKeypressContext(), { wrapper });
125
+ act(() => result.current.subscribe(keyHandler));
126
+ act(() => {
127
+ stdin.write('\x1b');
128
+ vi.advanceTimersByTime(10);
129
+ stdin.write('\x1b');
130
+ expect(keyHandler).not.toHaveBeenCalled();
131
+ vi.advanceTimersByTime(ESC_TIMEOUT);
132
+ expect(keyHandler).toHaveBeenNthCalledWith(1, expect.objectContaining({ name: 'escape', meta: true }));
133
+ expect(keyHandler).toHaveBeenNthCalledWith(2, expect.objectContaining({ name: 'escape', meta: true }));
134
+ });
135
+ });
130
136
  it('should handle lone Escape key (keycode 27) with timeout when kitty protocol is enabled', async () => {
131
137
  // Use real timers for this test to avoid issues with stream/buffer timing
132
- vi.useRealTimers();
133
138
  const keyHandler = vi.fn();
134
- const wrapper = ({ children }) => (_jsx(KeypressProvider, { kittyProtocolEnabled: true, children: children }));
139
+ const wrapper = ({ children }) => (_jsx(KeypressProvider, { children: children }));
135
140
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
136
141
  act(() => result.current.subscribe(keyHandler));
137
142
  // Send just ESC
138
143
  act(() => {
139
144
  stdin.write('\x1b');
140
- });
141
- // Should be buffered initially
142
- expect(keyHandler).not.toHaveBeenCalled();
143
- // Wait for timeout
144
- await waitFor(() => {
145
+ // Should be buffered initially
146
+ expect(keyHandler).not.toHaveBeenCalled();
147
+ vi.advanceTimersByTime(ESC_TIMEOUT + 10);
145
148
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
146
149
  name: 'escape',
147
150
  meta: true,
148
151
  }));
149
- }, { timeout: 500 });
152
+ });
150
153
  });
151
154
  });
152
155
  describe('Tab and Backspace handling', () => {
@@ -177,13 +180,12 @@ describe('KeypressContext - Kitty Protocol', () => {
177
180
  expected: { name: 'backspace', ctrl: true },
178
181
  },
179
182
  ])('should recognize $name in kitty protocol', async ({ sequence, expected }) => {
180
- const { keyHandler } = setupKeypressTest(true);
183
+ const { keyHandler } = setupKeypressTest();
181
184
  act(() => {
182
185
  stdin.write(sequence);
183
186
  });
184
187
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
185
188
  ...expected,
186
- kittyProtocol: true,
187
189
  }));
188
190
  });
189
191
  });
@@ -245,7 +247,7 @@ describe('KeypressContext - Kitty Protocol', () => {
245
247
  });
246
248
  it('should not log keystrokes when debugKeystrokeLogging is false', async () => {
247
249
  const keyHandler = vi.fn();
248
- const wrapper = ({ children }) => (_jsx(KeypressProvider, { kittyProtocolEnabled: true, debugKeystrokeLogging: false, children: children }));
250
+ const wrapper = ({ children }) => (_jsx(KeypressProvider, { debugKeystrokeLogging: false, children: children }));
249
251
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
250
252
  act(() => result.current.subscribe(keyHandler));
251
253
  // Send a kitty sequence
@@ -257,53 +259,22 @@ describe('KeypressContext - Kitty Protocol', () => {
257
259
  });
258
260
  it('should log kitty buffer accumulation when debugKeystrokeLogging is true', async () => {
259
261
  const keyHandler = vi.fn();
260
- const wrapper = ({ children }) => (_jsx(KeypressProvider, { kittyProtocolEnabled: true, debugKeystrokeLogging: true, children: children }));
262
+ const wrapper = ({ children }) => (_jsx(KeypressProvider, { debugKeystrokeLogging: true, children: children }));
261
263
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
262
264
  act(() => result.current.subscribe(keyHandler));
263
265
  // Send a complete kitty sequence for escape
264
266
  act(() => stdin.write('\x1b[27u'));
265
- expect(consoleLogSpy).toHaveBeenCalledWith('[DEBUG] Input buffer accumulating:', expect.stringContaining('"\\u001b[27u"'));
266
- const parsedCall = consoleLogSpy.mock.calls.find((args) => typeof args[0] === 'string' &&
267
- args[0].includes('[DEBUG] Sequence parsed successfully'));
268
- expect(parsedCall).toBeTruthy();
269
- expect(parsedCall?.[1]).toEqual(expect.stringContaining('\\u001b[27u'));
270
- });
271
- it('should log kitty buffer overflow when debugKeystrokeLogging is true', async () => {
272
- const keyHandler = vi.fn();
273
- const wrapper = ({ children }) => (_jsx(KeypressProvider, { kittyProtocolEnabled: true, debugKeystrokeLogging: true, children: children }));
274
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
275
- act(() => result.current.subscribe(keyHandler));
276
- // Send a long sequence starting with a valid kitty prefix to trigger overflow
277
- const longSequence = '\x1b[1;' + '1'.repeat(100);
278
- act(() => stdin.write(longSequence));
279
- expect(consoleLogSpy).toHaveBeenCalledWith('[DEBUG] Input buffer overflow, clearing:', expect.any(String));
280
- });
281
- it('should log kitty buffer clear on Ctrl+C when debugKeystrokeLogging is true', async () => {
282
- const keyHandler = vi.fn();
283
- const wrapper = ({ children }) => (_jsx(KeypressProvider, { kittyProtocolEnabled: true, debugKeystrokeLogging: true, children: children }));
284
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
285
- act(() => result.current.subscribe(keyHandler));
286
- act(() => stdin.write(INCOMPLETE_KITTY_SEQUENCE));
287
- // Send Ctrl+C
288
- act(() => stdin.write('\x03'));
289
- expect(consoleLogSpy).toHaveBeenCalledWith('[DEBUG] Input buffer cleared on Ctrl+C:', INCOMPLETE_KITTY_SEQUENCE);
290
- // Verify Ctrl+C was handled
291
- expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
292
- name: 'c',
293
- ctrl: true,
294
- }));
267
+ expect(consoleLogSpy).toHaveBeenCalledWith(`[DEBUG] Raw StdIn: ${JSON.stringify('\x1b[27u')}`);
295
268
  });
296
269
  it('should show char codes when debugKeystrokeLogging is true even without debug mode', async () => {
297
270
  const keyHandler = vi.fn();
298
- const wrapper = ({ children }) => (_jsx(KeypressProvider, { kittyProtocolEnabled: true, debugKeystrokeLogging: true, children: children }));
271
+ const wrapper = ({ children }) => (_jsx(KeypressProvider, { debugKeystrokeLogging: true, children: children }));
299
272
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
300
273
  act(() => result.current.subscribe(keyHandler));
301
274
  // Send incomplete kitty sequence
302
275
  act(() => stdin.write(INCOMPLETE_KITTY_SEQUENCE));
303
276
  // Verify debug logging for accumulation
304
- expect(consoleLogSpy).toHaveBeenCalledWith('[DEBUG] Input buffer accumulating:', JSON.stringify(INCOMPLETE_KITTY_SEQUENCE));
305
- // Verify warning for char codes
306
- expect(consoleWarnSpy).toHaveBeenCalledWith('Input sequence buffer has content:', JSON.stringify(INCOMPLETE_KITTY_SEQUENCE));
277
+ expect(consoleLogSpy).toHaveBeenCalledWith(`[DEBUG] Raw StdIn: ${JSON.stringify(INCOMPLETE_KITTY_SEQUENCE)}`);
307
278
  });
308
279
  });
309
280
  describe('Parameterized functional keys', () => {
@@ -362,106 +333,18 @@ describe('KeypressContext - Kitty Protocol', () => {
362
333
  });
363
334
  describe('Double-tap and batching', () => {
364
335
  it('should emit two delete events for double-tap CSI[3~', async () => {
365
- const { keyHandler } = setupKeypressTest(true);
336
+ const { keyHandler } = setupKeypressTest();
366
337
  act(() => stdin.write(`\x1b[3~`));
367
338
  act(() => stdin.write(`\x1b[3~`));
368
339
  expect(keyHandler).toHaveBeenNthCalledWith(1, expect.objectContaining({ name: 'delete' }));
369
340
  expect(keyHandler).toHaveBeenNthCalledWith(2, expect.objectContaining({ name: 'delete' }));
370
341
  });
371
342
  it('should parse two concatenated tilde-coded sequences in one chunk', async () => {
372
- const { keyHandler } = setupKeypressTest(true);
343
+ const { keyHandler } = setupKeypressTest();
373
344
  act(() => stdin.write(`\x1b[3~\x1b[5~`));
374
345
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({ name: 'delete' }));
375
346
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({ name: 'pageup' }));
376
347
  });
377
- it('should ignore incomplete CSI then parse the next complete sequence', async () => {
378
- const { keyHandler } = setupKeypressTest(true);
379
- // Incomplete ESC sequence then a complete Delete
380
- act(() => {
381
- // Provide an incomplete ESC sequence chunk with a real ESC character
382
- stdin.write('\x1b[1;');
383
- });
384
- act(() => stdin.write(`\x1b[3~`));
385
- expect(keyHandler).toHaveBeenCalledTimes(1);
386
- expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({ name: 'delete' }));
387
- });
388
- });
389
- });
390
- describe('Drag and Drop Handling', () => {
391
- let stdin;
392
- const mockSetRawMode = vi.fn();
393
- const wrapper = ({ children, kittyProtocolEnabled = true, }) => (_jsx(KeypressProvider, { kittyProtocolEnabled: kittyProtocolEnabled, children: children }));
394
- beforeEach(() => {
395
- vi.clearAllMocks();
396
- vi.useFakeTimers();
397
- stdin = new MockStdin();
398
- useStdin.mockReturnValue({
399
- stdin,
400
- setRawMode: mockSetRawMode,
401
- });
402
- });
403
- afterEach(() => {
404
- vi.useRealTimers();
405
- });
406
- describe('drag start by quotes', () => {
407
- it.each([
408
- { name: 'single quote', quote: SINGLE_QUOTE },
409
- { name: 'double quote', quote: DOUBLE_QUOTE },
410
- ])('should start collecting when $name arrives and not broadcast immediately', async ({ quote }) => {
411
- const keyHandler = vi.fn();
412
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
413
- act(() => result.current.subscribe(keyHandler));
414
- act(() => stdin.write(quote));
415
- expect(keyHandler).not.toHaveBeenCalled();
416
- });
417
- });
418
- describe('drag collection and completion', () => {
419
- it.each([
420
- {
421
- name: 'collect single character inputs during drag mode',
422
- characters: ['a'],
423
- expectedText: 'a',
424
- },
425
- {
426
- name: 'collect multiple characters and complete on timeout',
427
- characters: ['p', 'a', 't', 'h'],
428
- expectedText: 'path',
429
- },
430
- ])('should $name', async ({ characters, expectedText }) => {
431
- const keyHandler = vi.fn();
432
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
433
- act(() => result.current.subscribe(keyHandler));
434
- act(() => stdin.write(SINGLE_QUOTE));
435
- characters.forEach((char) => {
436
- act(() => stdin.write(char));
437
- });
438
- expect(keyHandler).not.toHaveBeenCalled();
439
- act(() => {
440
- vi.advanceTimersByTime(DRAG_COMPLETION_TIMEOUT_MS + 10);
441
- });
442
- expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
443
- name: '',
444
- paste: true,
445
- sequence: `${SINGLE_QUOTE}${expectedText}`,
446
- }));
447
- });
448
- });
449
- });
450
- describe('Kitty Sequence Parsing', () => {
451
- let stdin;
452
- const mockSetRawMode = vi.fn();
453
- const wrapper = ({ children, kittyProtocolEnabled = true, }) => (_jsx(KeypressProvider, { kittyProtocolEnabled: kittyProtocolEnabled, children: children }));
454
- beforeEach(() => {
455
- vi.clearAllMocks();
456
- vi.useFakeTimers();
457
- stdin = new MockStdin();
458
- useStdin.mockReturnValue({
459
- stdin,
460
- setRawMode: mockSetRawMode,
461
- });
462
- });
463
- afterEach(() => {
464
- vi.useRealTimers();
465
348
  });
466
349
  describe('Cross-terminal Alt key handling (simulating macOS)', () => {
467
350
  let originalPlatform;
@@ -499,7 +382,6 @@ describe('Kitty Sequence Parsing', () => {
499
382
  meta: true,
500
383
  shift: false,
501
384
  paste: false,
502
- kittyProtocol: true,
503
385
  },
504
386
  };
505
387
  }
@@ -538,9 +420,9 @@ describe('Kitty Sequence Parsing', () => {
538
420
  },
539
421
  };
540
422
  }
541
- })))('should handle Alt+$key in $terminal', ({ chunk, expected, kitty = true, }) => {
423
+ })))('should handle Alt+$key in $terminal', ({ chunk, expected }) => {
542
424
  const keyHandler = vi.fn();
543
- const testWrapper = ({ children }) => (_jsx(KeypressProvider, { kittyProtocolEnabled: kitty, children: children }));
425
+ const testWrapper = ({ children }) => (_jsx(KeypressProvider, { children: children }));
544
426
  const { result } = renderHook(() => useKeypressContext(), {
545
427
  wrapper: testWrapper,
546
428
  });
@@ -550,14 +432,8 @@ describe('Kitty Sequence Parsing', () => {
550
432
  });
551
433
  });
552
434
  describe('Backslash key handling', () => {
553
- beforeEach(() => {
554
- vi.useFakeTimers();
555
- });
556
- afterEach(() => {
557
- vi.useRealTimers();
558
- });
559
435
  it('should treat backslash as a regular keystroke', () => {
560
- const { keyHandler } = setupKeypressTest(true);
436
+ const { keyHandler } = setupKeypressTest();
561
437
  act(() => stdin.write('\\'));
562
438
  // Advance timers to trigger the backslash timeout
563
439
  act(() => {
@@ -577,14 +453,14 @@ describe('Kitty Sequence Parsing', () => {
577
453
  // Should not broadcast immediately
578
454
  expect(keyHandler).not.toHaveBeenCalled();
579
455
  // Advance time just before timeout
580
- act(() => vi.advanceTimersByTime(KITTY_SEQUENCE_TIMEOUT_MS - 5));
456
+ act(() => vi.advanceTimersByTime(ESC_TIMEOUT - 5));
581
457
  // Still shouldn't broadcast
582
458
  expect(keyHandler).not.toHaveBeenCalled();
583
459
  // Advance past timeout
584
460
  act(() => vi.advanceTimersByTime(10));
585
461
  // Should now broadcast the incomplete sequence as regular input
586
462
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
587
- name: '',
463
+ name: 'undefined',
588
464
  sequence: INCOMPLETE_KITTY_SEQUENCE,
589
465
  paste: false,
590
466
  }));
@@ -612,7 +488,6 @@ describe('Kitty Sequence Parsing', () => {
612
488
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
613
489
  name: 'a',
614
490
  ctrl: true,
615
- kittyProtocol: true,
616
491
  }));
617
492
  });
618
493
  it('should handle batched kitty sequences correctly', async () => {
@@ -626,28 +501,10 @@ describe('Kitty Sequence Parsing', () => {
626
501
  expect(keyHandler).toHaveBeenNthCalledWith(1, expect.objectContaining({
627
502
  name: 'a',
628
503
  ctrl: true,
629
- kittyProtocol: true,
630
504
  }));
631
505
  expect(keyHandler).toHaveBeenNthCalledWith(2, expect.objectContaining({
632
506
  name: 'b',
633
507
  ctrl: true,
634
- kittyProtocol: true,
635
- }));
636
- });
637
- it('should clear kitty buffer and timeout on Ctrl+C', async () => {
638
- const keyHandler = vi.fn();
639
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
640
- act(() => result.current.subscribe(keyHandler));
641
- act(() => stdin.write(INCOMPLETE_KITTY_SEQUENCE));
642
- // Press Ctrl+C
643
- act(() => stdin.write('\x03'));
644
- // Advance past timeout
645
- act(() => vi.advanceTimersByTime(KITTY_SEQUENCE_TIMEOUT_MS + 10));
646
- // Should only have received Ctrl+C, not the incomplete sequence
647
- expect(keyHandler).toHaveBeenCalledTimes(1);
648
- expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
649
- name: 'c',
650
- ctrl: true,
651
508
  }));
652
509
  });
653
510
  it('should handle mixed valid and invalid sequences', async () => {
@@ -661,49 +518,25 @@ describe('Kitty Sequence Parsing', () => {
661
518
  expect(keyHandler).toHaveBeenCalledTimes(2);
662
519
  expect(keyHandler).toHaveBeenNthCalledWith(1, expect.objectContaining({
663
520
  name: 'return',
664
- kittyProtocol: true,
665
521
  }));
666
522
  expect(keyHandler).toHaveBeenNthCalledWith(2, expect.objectContaining({
667
523
  sequence: '\x1b[!',
668
524
  }));
669
525
  });
670
- it('should not buffer sequences when kitty protocol is disabled', async () => {
671
- const keyHandler = vi.fn();
672
- const { result } = renderHook(() => useKeypressContext(), {
673
- wrapper: ({ children }) => wrapper({ children, kittyProtocolEnabled: false }),
674
- });
675
- act(() => result.current.subscribe(keyHandler));
676
- // Send what would be a kitty sequence
677
- act(() => stdin.write('\x1b[13u'));
678
- // Should pass through without parsing
679
- expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
680
- sequence: '\x1b[13u',
681
- }));
682
- expect(keyHandler).not.toHaveBeenCalledWith(expect.objectContaining({
683
- name: 'return',
684
- kittyProtocol: true,
685
- }));
686
- });
687
- it('should handle sequences arriving character by character', async () => {
688
- vi.useRealTimers(); // Required for correct buffering timing.
526
+ it.each([1, ESC_TIMEOUT - 1])('should handle sequences arriving character by character with %s ms delay', async (delay) => {
689
527
  const keyHandler = vi.fn();
690
528
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
691
- act(() => {
692
- result.current.subscribe(keyHandler);
693
- });
529
+ act(() => result.current.subscribe(keyHandler));
694
530
  // Send kitty sequence character by character
695
- const sequence = '\x1b[27u'; // Escape key
696
- for (const char of sequence) {
697
- act(() => {
698
- stdin.emit('data', Buffer.from(char));
699
- });
700
- await new Promise((resolve) => setImmediate(resolve));
531
+ for (const char of '\x1b[27u') {
532
+ act(() => stdin.write(char));
533
+ // Advance time but not enough to timeout
534
+ vi.advanceTimersByTime(delay);
701
535
  }
702
536
  // Should parse once complete
703
537
  await waitFor(() => {
704
538
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
705
539
  name: 'escape',
706
- kittyProtocol: true,
707
540
  }));
708
541
  });
709
542
  });
@@ -726,76 +559,8 @@ describe('Kitty Sequence Parsing', () => {
726
559
  // Should now parse as complete enter key
727
560
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
728
561
  name: 'a',
729
- kittyProtocol: true,
730
- }));
731
- });
732
- it('should flush incomplete kitty sequence on FOCUS_IN event', async () => {
733
- const keyHandler = vi.fn();
734
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
735
- act(() => result.current.subscribe(keyHandler));
736
- act(() => stdin.write(INCOMPLETE_KITTY_SEQUENCE));
737
- // Incomplete sequence should be buffered, not broadcast
738
- expect(keyHandler).not.toHaveBeenCalled();
739
- // Send FOCUS_IN event
740
- act(() => stdin.write('\x1b[I'));
741
- // The buffered sequence should be flushed
742
- expect(keyHandler).toHaveBeenCalledTimes(1);
743
- expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
744
- name: '',
745
- sequence: INCOMPLETE_KITTY_SEQUENCE,
746
- paste: false,
747
562
  }));
748
563
  });
749
- it('should flush incomplete kitty sequence on FOCUS_OUT event', async () => {
750
- const keyHandler = vi.fn();
751
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
752
- act(() => result.current.subscribe(keyHandler));
753
- act(() => stdin.write(INCOMPLETE_KITTY_SEQUENCE));
754
- // Incomplete sequence should be buffered, not broadcast
755
- expect(keyHandler).not.toHaveBeenCalled();
756
- // Send FOCUS_OUT event
757
- act(() => stdin.write('\x1b[O'));
758
- // The buffered sequence should be flushed
759
- expect(keyHandler).toHaveBeenCalledTimes(1);
760
- expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
761
- name: '',
762
- sequence: INCOMPLETE_KITTY_SEQUENCE,
763
- paste: false,
764
- }));
765
- });
766
- it('should flush incomplete kitty sequence on paste event', async () => {
767
- vi.useFakeTimers();
768
- const keyHandler = vi.fn();
769
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
770
- act(() => result.current.subscribe(keyHandler));
771
- act(() => stdin.write(INCOMPLETE_KITTY_SEQUENCE));
772
- // Incomplete sequence should be buffered, not broadcast
773
- expect(keyHandler).not.toHaveBeenCalled();
774
- // Send paste start sequence
775
- act(() => stdin.write(`\x1b[200~`));
776
- // The buffered sequence should be flushed
777
- expect(keyHandler).toHaveBeenCalledTimes(1);
778
- expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
779
- name: '',
780
- sequence: INCOMPLETE_KITTY_SEQUENCE,
781
- paste: false,
782
- }));
783
- // Now send some paste content and end paste to make sure paste still works
784
- const pastedText = 'hello';
785
- const PASTE_MODE_SUFFIX = `\x1b[201~`;
786
- act(() => {
787
- stdin.write(pastedText);
788
- stdin.write(PASTE_MODE_SUFFIX);
789
- });
790
- act(() => vi.runAllTimers());
791
- // The paste event should be broadcast
792
- expect(keyHandler).toHaveBeenCalledTimes(2);
793
- expect(keyHandler).toHaveBeenNthCalledWith(2, expect.objectContaining({
794
- paste: true,
795
- sequence: pastedText,
796
- }));
797
- vi.useRealTimers();
798
- });
799
564
  describe('SGR Mouse Handling', () => {
800
565
  it('should ignore SGR mouse sequences', async () => {
801
566
  const keyHandler = vi.fn();
@@ -834,26 +599,22 @@ describe('Kitty Sequence Parsing', () => {
834
599
  // Send X11 mouse sequence: ESC [ M followed by 3 bytes
835
600
  // Space is 32. 32+0=32 (button 0), 32+33=65 ('A', col 33), 32+34=66 ('B', row 34)
836
601
  const x11Seq = '\x1b[M AB';
837
- act(() => {
838
- stdin.write(x11Seq);
839
- });
602
+ act(() => stdin.write(x11Seq));
840
603
  // Should not broadcast as keystrokes
841
604
  expect(keyHandler).not.toHaveBeenCalled();
842
605
  });
843
606
  it('should not flush slow SGR mouse sequences as garbage', async () => {
844
- vi.useFakeTimers();
845
607
  const keyHandler = vi.fn();
846
608
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
847
609
  act(() => result.current.subscribe(keyHandler));
848
610
  // Send start of SGR sequence
849
611
  act(() => stdin.write('\x1b[<'));
850
612
  // Advance time past the normal kitty timeout (50ms)
851
- act(() => vi.advanceTimersByTime(KITTY_SEQUENCE_TIMEOUT_MS + 10));
613
+ act(() => vi.advanceTimersByTime(ESC_TIMEOUT + 10));
852
614
  // Send the rest
853
615
  act(() => stdin.write('0;37;25M'));
854
616
  // Should NOT have flushed the prefix as garbage, and should have consumed the whole thing
855
617
  expect(keyHandler).not.toHaveBeenCalled();
856
- vi.useRealTimers();
857
618
  });
858
619
  it('should ignore specific SGR mouse sequence sandwiched between keystrokes', async () => {
859
620
  const keyHandler = vi.fn();
@@ -870,41 +631,31 @@ describe('Kitty Sequence Parsing', () => {
870
631
  });
871
632
  });
872
633
  describe('Ignored Sequences', () => {
873
- describe.each([true, false])('with kittyProtocolEnabled = %s', (kittyEnabled) => {
874
- it.each([
875
- { name: 'Focus In', sequence: '\x1b[I' },
876
- { name: 'Focus Out', sequence: '\x1b[O' },
877
- { name: 'SGR Mouse Release', sequence: '\u001b[<0;44;18m' },
878
- { name: 'something mouse', sequence: '\u001b[<0;53;19M' },
879
- { name: 'another mouse', sequence: '\u001b[<0;29;19m' },
880
- ])('should ignore $name sequence', async ({ sequence }) => {
881
- vi.useFakeTimers();
882
- const keyHandler = vi.fn();
883
- const wrapper = ({ children }) => (_jsx(KeypressProvider, { kittyProtocolEnabled: kittyEnabled, children: children }));
884
- const { result } = renderHook(() => useKeypressContext(), {
885
- wrapper,
886
- });
887
- act(() => result.current.subscribe(keyHandler));
888
- for (const char of sequence) {
889
- act(() => {
890
- stdin.write(char);
891
- });
892
- await act(async () => {
893
- vi.advanceTimersByTime(0);
894
- });
895
- }
896
- act(() => {
897
- stdin.write('HI');
898
- });
899
- expect(keyHandler).toHaveBeenCalledTimes(2);
900
- expect(keyHandler).toHaveBeenNthCalledWith(1, expect.objectContaining({ name: 'h', sequence: 'H', shift: true }));
901
- expect(keyHandler).toHaveBeenNthCalledWith(2, expect.objectContaining({ name: 'i', sequence: 'I', shift: true }));
902
- vi.useRealTimers();
634
+ it.each([
635
+ { name: 'Focus In', sequence: '\x1b[I' },
636
+ { name: 'Focus Out', sequence: '\x1b[O' },
637
+ { name: 'SGR Mouse Release', sequence: '\u001b[<0;44;18m' },
638
+ { name: 'something mouse', sequence: '\u001b[<0;53;19M' },
639
+ { name: 'another mouse', sequence: '\u001b[<0;29;19m' },
640
+ ])('should ignore $name sequence', async ({ sequence }) => {
641
+ const keyHandler = vi.fn();
642
+ const wrapper = ({ children }) => (_jsx(KeypressProvider, { children: children }));
643
+ const { result } = renderHook(() => useKeypressContext(), {
644
+ wrapper,
903
645
  });
646
+ act(() => result.current.subscribe(keyHandler));
647
+ for (const char of sequence) {
648
+ act(() => stdin.write(char));
649
+ act(() => vi.advanceTimersByTime(0));
650
+ }
651
+ act(() => stdin.write('HI'));
652
+ expect(keyHandler).toHaveBeenCalledTimes(2);
653
+ expect(keyHandler).toHaveBeenNthCalledWith(1, expect.objectContaining({ name: 'h', sequence: 'H', shift: true }));
654
+ expect(keyHandler).toHaveBeenNthCalledWith(2, expect.objectContaining({ name: 'i', sequence: 'I', shift: true }));
904
655
  });
905
- it('should handle F12 when kittyProtocolEnabled is false', async () => {
656
+ it('should handle F12', async () => {
906
657
  const keyHandler = vi.fn();
907
- const wrapper = ({ children }) => (_jsx(KeypressProvider, { kittyProtocolEnabled: false, children: children }));
658
+ const wrapper = ({ children }) => (_jsx(KeypressProvider, { children: children }));
908
659
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
909
660
  act(() => result.current.subscribe(keyHandler));
910
661
  act(() => {
@@ -913,5 +664,23 @@ describe('Kitty Sequence Parsing', () => {
913
664
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({ name: 'f12', sequence: '\u001b[24~' }));
914
665
  });
915
666
  });
667
+ describe('Individual Character Input', () => {
668
+ it.each([
669
+ 'abc', // ASCII character
670
+ '你好', // Chinese characters
671
+ 'こんにちは', // Japanese characters
672
+ '안녕하세요', // Korean characters
673
+ 'A你B好C', // Mixed characters
674
+ ])('should correctly handle string "%s"', async (inputString) => {
675
+ const keyHandler = vi.fn();
676
+ const { result } = renderHook(() => useKeypressContext(), { wrapper });
677
+ act(() => result.current.subscribe(keyHandler));
678
+ act(() => stdin.write(inputString));
679
+ expect(keyHandler).toHaveBeenCalledTimes(inputString.length);
680
+ for (const char of inputString) {
681
+ expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({ sequence: char }));
682
+ }
683
+ });
684
+ });
916
685
  });
917
686
  //# sourceMappingURL=KeypressContext.test.js.map