@google/gemini-cli 0.12.0-nightly.20251023.a7faa208 → 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.
Files changed (233) hide show
  1. package/dist/package.json +5 -4
  2. package/dist/src/commands/extensions/disable.js +13 -6
  3. package/dist/src/commands/extensions/disable.js.map +1 -1
  4. package/dist/src/commands/extensions/enable.js +13 -6
  5. package/dist/src/commands/extensions/enable.js.map +1 -1
  6. package/dist/src/commands/extensions/install.js +12 -2
  7. package/dist/src/commands/extensions/install.js.map +1 -1
  8. package/dist/src/commands/extensions/install.test.js +11 -3
  9. package/dist/src/commands/extensions/install.test.js.map +1 -1
  10. package/dist/src/commands/extensions/link.js +12 -2
  11. package/dist/src/commands/extensions/link.js.map +1 -1
  12. package/dist/src/commands/extensions/list.js +13 -4
  13. package/dist/src/commands/extensions/list.js.map +1 -1
  14. package/dist/src/commands/extensions/uninstall.js +12 -2
  15. package/dist/src/commands/extensions/uninstall.js.map +1 -1
  16. package/dist/src/commands/extensions/update.js +17 -13
  17. package/dist/src/commands/extensions/update.js.map +1 -1
  18. package/dist/src/commands/mcp/list.js +10 -3
  19. package/dist/src/commands/mcp/list.js.map +1 -1
  20. package/dist/src/commands/mcp/list.test.js +12 -6
  21. package/dist/src/commands/mcp/list.test.js.map +1 -1
  22. package/dist/src/config/config.d.ts +1 -0
  23. package/dist/src/config/config.js +15 -3
  24. package/dist/src/config/config.js.map +1 -1
  25. package/dist/src/config/config.test.js +23 -15
  26. package/dist/src/config/config.test.js.map +1 -1
  27. package/dist/src/config/extension-manager.d.ts +38 -0
  28. package/dist/src/config/extension-manager.js +411 -0
  29. package/dist/src/config/extension-manager.js.map +1 -0
  30. package/dist/src/config/extension.d.ts +4 -51
  31. package/dist/src/config/extension.js +1 -535
  32. package/dist/src/config/extension.js.map +1 -1
  33. package/dist/src/config/extension.test.js +343 -163
  34. package/dist/src/config/extension.test.js.map +1 -1
  35. package/dist/src/config/extensions/consent.d.ts +38 -0
  36. package/dist/src/config/extensions/consent.js +123 -0
  37. package/dist/src/config/extensions/consent.js.map +1 -0
  38. package/dist/src/config/extensions/extensionEnablement.d.ts +1 -1
  39. package/dist/src/config/extensions/extensionEnablement.js +4 -3
  40. package/dist/src/config/extensions/extensionEnablement.js.map +1 -1
  41. package/dist/src/config/extensions/extensionEnablement.test.js +10 -10
  42. package/dist/src/config/extensions/extensionEnablement.test.js.map +1 -1
  43. package/dist/src/config/extensions/extensionSettings.d.ts +15 -0
  44. package/dist/src/config/extensions/extensionSettings.js +63 -0
  45. package/dist/src/config/extensions/extensionSettings.js.map +1 -0
  46. package/dist/src/config/extensions/extensionSettings.test.d.ts +6 -0
  47. package/dist/src/config/extensions/extensionSettings.test.js +137 -0
  48. package/dist/src/config/extensions/extensionSettings.test.js.map +1 -0
  49. package/dist/src/config/extensions/github.d.ts +2 -2
  50. package/dist/src/config/extensions/github.js +3 -8
  51. package/dist/src/config/extensions/github.js.map +1 -1
  52. package/dist/src/config/extensions/github.test.js +25 -7
  53. package/dist/src/config/extensions/github.test.js.map +1 -1
  54. package/dist/src/config/extensions/github_fetch.d.ts +1 -1
  55. package/dist/src/config/extensions/github_fetch.js +13 -1
  56. package/dist/src/config/extensions/github_fetch.js.map +1 -1
  57. package/dist/src/config/extensions/github_fetch.test.d.ts +6 -0
  58. package/dist/src/config/extensions/github_fetch.test.js +169 -0
  59. package/dist/src/config/extensions/github_fetch.test.js.map +1 -0
  60. package/dist/src/config/extensions/storage.d.ts +14 -0
  61. package/dist/src/config/extensions/storage.js +32 -0
  62. package/dist/src/config/extensions/storage.js.map +1 -0
  63. package/dist/src/config/extensions/update.d.ts +4 -4
  64. package/dist/src/config/extensions/update.js +11 -18
  65. package/dist/src/config/extensions/update.js.map +1 -1
  66. package/dist/src/config/extensions/update.test.js +33 -58
  67. package/dist/src/config/extensions/update.test.js.map +1 -1
  68. package/dist/src/config/extensions/variableSchema.d.ts +0 -6
  69. package/dist/src/config/extensions/variableSchema.js.map +1 -1
  70. package/dist/src/config/extensions/variables.d.ts +4 -0
  71. package/dist/src/config/extensions/variables.js +6 -0
  72. package/dist/src/config/extensions/variables.js.map +1 -1
  73. package/dist/src/config/settings.d.ts +2 -1
  74. package/dist/src/config/settings.js +4 -7
  75. package/dist/src/config/settings.js.map +1 -1
  76. package/dist/src/config/settings.test.js +86 -16
  77. package/dist/src/config/settings.test.js.map +1 -1
  78. package/dist/src/core/initializer.js +2 -1
  79. package/dist/src/core/initializer.js.map +1 -1
  80. package/dist/src/gemini.js +22 -5
  81. package/dist/src/gemini.js.map +1 -1
  82. package/dist/src/gemini.test.js +26 -2
  83. package/dist/src/gemini.test.js.map +1 -1
  84. package/dist/src/generated/git-commit.d.ts +2 -2
  85. package/dist/src/generated/git-commit.js +2 -2
  86. package/dist/src/nonInteractiveCli.js +30 -5
  87. package/dist/src/nonInteractiveCli.js.map +1 -1
  88. package/dist/src/nonInteractiveCli.test.js +151 -14
  89. package/dist/src/nonInteractiveCli.test.js.map +1 -1
  90. package/dist/src/test-utils/createExtension.d.ts +3 -1
  91. package/dist/src/test-utils/createExtension.js +3 -3
  92. package/dist/src/test-utils/createExtension.js.map +1 -1
  93. package/dist/src/ui/AppContainer.js +102 -48
  94. package/dist/src/ui/AppContainer.js.map +1 -1
  95. package/dist/src/ui/AppContainer.test.js +138 -79
  96. package/dist/src/ui/AppContainer.test.js.map +1 -1
  97. package/dist/src/ui/commands/extensionsCommand.js +19 -10
  98. package/dist/src/ui/commands/extensionsCommand.js.map +1 -1
  99. package/dist/src/ui/commands/extensionsCommand.test.js +8 -0
  100. package/dist/src/ui/commands/extensionsCommand.test.js.map +1 -1
  101. package/dist/src/ui/components/FolderTrustDialog.test.js +1 -0
  102. package/dist/src/ui/components/FolderTrustDialog.test.js.map +1 -1
  103. package/dist/src/ui/components/HistoryItemDisplay.js +1 -1
  104. package/dist/src/ui/components/HistoryItemDisplay.js.map +1 -1
  105. package/dist/src/ui/components/InputPrompt.js +5 -6
  106. package/dist/src/ui/components/InputPrompt.js.map +1 -1
  107. package/dist/src/ui/components/InputPrompt.test.js +668 -712
  108. package/dist/src/ui/components/InputPrompt.test.js.map +1 -1
  109. package/dist/src/ui/components/ModelDialog.test.js +1 -0
  110. package/dist/src/ui/components/ModelDialog.test.js.map +1 -1
  111. package/dist/src/ui/components/PermissionsModifyTrustDialog.test.js +1 -0
  112. package/dist/src/ui/components/PermissionsModifyTrustDialog.test.js.map +1 -1
  113. package/dist/src/ui/components/SettingsDialog.test.js +2 -30
  114. package/dist/src/ui/components/SettingsDialog.test.js.map +1 -1
  115. package/dist/src/ui/components/ThemeDialog.test.js +1 -2
  116. package/dist/src/ui/components/ThemeDialog.test.js.map +1 -1
  117. package/dist/src/ui/components/shared/BaseSelectionList.test.js +1 -0
  118. package/dist/src/ui/components/shared/BaseSelectionList.test.js.map +1 -1
  119. package/dist/src/ui/components/shared/text-buffer.test.js +1 -0
  120. package/dist/src/ui/components/shared/text-buffer.test.js.map +1 -1
  121. package/dist/src/ui/components/views/ExtensionsList.d.ts +7 -1
  122. package/dist/src/ui/components/views/ExtensionsList.js +8 -11
  123. package/dist/src/ui/components/views/ExtensionsList.js.map +1 -1
  124. package/dist/src/ui/components/views/ExtensionsList.test.js +34 -21
  125. package/dist/src/ui/components/views/ExtensionsList.test.js.map +1 -1
  126. package/dist/src/ui/contexts/KeypressContext.d.ts +3 -2
  127. package/dist/src/ui/contexts/KeypressContext.js +429 -386
  128. package/dist/src/ui/contexts/KeypressContext.js.map +1 -1
  129. package/dist/src/ui/contexts/KeypressContext.test.js +162 -479
  130. package/dist/src/ui/contexts/KeypressContext.test.js.map +1 -1
  131. package/dist/src/ui/contexts/SessionContext.test.js +1 -0
  132. package/dist/src/ui/contexts/SessionContext.test.js.map +1 -1
  133. package/dist/src/ui/hooks/shellCommandProcessor.test.js +18 -2
  134. package/dist/src/ui/hooks/shellCommandProcessor.test.js.map +1 -1
  135. package/dist/src/ui/hooks/slashCommandProcessor.test.js +74 -80
  136. package/dist/src/ui/hooks/slashCommandProcessor.test.js.map +1 -1
  137. package/dist/src/ui/hooks/useAutoAcceptIndicator.test.js +1 -0
  138. package/dist/src/ui/hooks/useAutoAcceptIndicator.test.js.map +1 -1
  139. package/dist/src/ui/hooks/useCommandCompletion.test.js +79 -78
  140. package/dist/src/ui/hooks/useCommandCompletion.test.js.map +1 -1
  141. package/dist/src/ui/hooks/useConsoleMessages.test.js +26 -9
  142. package/dist/src/ui/hooks/useConsoleMessages.test.js.map +1 -1
  143. package/dist/src/ui/hooks/useEditorSettings.test.js +40 -34
  144. package/dist/src/ui/hooks/useEditorSettings.test.js.map +1 -1
  145. package/dist/src/ui/hooks/useExtensionUpdates.d.ts +15 -5
  146. package/dist/src/ui/hooks/useExtensionUpdates.js +16 -18
  147. package/dist/src/ui/hooks/useExtensionUpdates.js.map +1 -1
  148. package/dist/src/ui/hooks/useExtensionUpdates.test.js +46 -36
  149. package/dist/src/ui/hooks/useExtensionUpdates.test.js.map +1 -1
  150. package/dist/src/ui/hooks/useFlickerDetector.test.js +1 -0
  151. package/dist/src/ui/hooks/useFlickerDetector.test.js.map +1 -1
  152. package/dist/src/ui/hooks/useFocus.test.js +25 -9
  153. package/dist/src/ui/hooks/useFocus.test.js.map +1 -1
  154. package/dist/src/ui/hooks/useFolderTrust.test.js +1 -0
  155. package/dist/src/ui/hooks/useFolderTrust.test.js.map +1 -1
  156. package/dist/src/ui/hooks/useGeminiStream.js +49 -14
  157. package/dist/src/ui/hooks/useGeminiStream.js.map +1 -1
  158. package/dist/src/ui/hooks/useGeminiStream.test.js +114 -34
  159. package/dist/src/ui/hooks/useGeminiStream.test.js.map +1 -1
  160. package/dist/src/ui/hooks/useGitBranchName.js +4 -0
  161. package/dist/src/ui/hooks/useGitBranchName.js.map +1 -1
  162. package/dist/src/ui/hooks/useGitBranchName.test.js +46 -35
  163. package/dist/src/ui/hooks/useGitBranchName.test.js.map +1 -1
  164. package/dist/src/ui/hooks/useHistoryManager.test.js +1 -0
  165. package/dist/src/ui/hooks/useHistoryManager.test.js.map +1 -1
  166. package/dist/src/ui/hooks/useIdeTrustListener.test.js +24 -7
  167. package/dist/src/ui/hooks/useIdeTrustListener.test.js.map +1 -1
  168. package/dist/src/ui/hooks/useInputHistory.test.js +1 -0
  169. package/dist/src/ui/hooks/useInputHistory.test.js.map +1 -1
  170. package/dist/src/ui/hooks/useInputHistoryStore.test.js +1 -0
  171. package/dist/src/ui/hooks/useInputHistoryStore.test.js.map +1 -1
  172. package/dist/src/ui/hooks/useKeypress.test.js +94 -113
  173. package/dist/src/ui/hooks/useKeypress.test.js.map +1 -1
  174. package/dist/src/ui/hooks/useLoadingIndicator.test.js +24 -6
  175. package/dist/src/ui/hooks/useLoadingIndicator.test.js.map +1 -1
  176. package/dist/src/ui/hooks/useMemoryMonitor.test.js +10 -5
  177. package/dist/src/ui/hooks/useMemoryMonitor.test.js.map +1 -1
  178. package/dist/src/ui/hooks/useMessageQueue.test.js +61 -45
  179. package/dist/src/ui/hooks/useMessageQueue.test.js.map +1 -1
  180. package/dist/src/ui/hooks/useModelCommand.test.js +18 -11
  181. package/dist/src/ui/hooks/useModelCommand.test.js.map +1 -1
  182. package/dist/src/ui/hooks/usePermissionsModifyTrust.test.js +1 -0
  183. package/dist/src/ui/hooks/usePermissionsModifyTrust.test.js.map +1 -1
  184. package/dist/src/ui/hooks/usePhraseCycler.test.js +1 -0
  185. package/dist/src/ui/hooks/usePhraseCycler.test.js.map +1 -1
  186. package/dist/src/ui/hooks/usePrivacySettings.test.js +26 -10
  187. package/dist/src/ui/hooks/usePrivacySettings.test.js.map +1 -1
  188. package/dist/src/ui/hooks/useQuotaAndFallback.js +13 -14
  189. package/dist/src/ui/hooks/useQuotaAndFallback.js.map +1 -1
  190. package/dist/src/ui/hooks/useQuotaAndFallback.test.js +32 -39
  191. package/dist/src/ui/hooks/useQuotaAndFallback.test.js.map +1 -1
  192. package/dist/src/ui/hooks/useReactToolScheduler.d.ts +8 -1
  193. package/dist/src/ui/hooks/useReactToolScheduler.js +59 -34
  194. package/dist/src/ui/hooks/useReactToolScheduler.js.map +1 -1
  195. package/dist/src/ui/hooks/useReactToolScheduler.test.d.ts +6 -0
  196. package/dist/src/ui/hooks/useReactToolScheduler.test.js +66 -0
  197. package/dist/src/ui/hooks/useReactToolScheduler.test.js.map +1 -0
  198. package/dist/src/ui/hooks/useSelectionList.test.js +193 -132
  199. package/dist/src/ui/hooks/useSelectionList.test.js.map +1 -1
  200. package/dist/src/ui/hooks/useShellHistory.test.js +1 -0
  201. package/dist/src/ui/hooks/useShellHistory.test.js.map +1 -1
  202. package/dist/src/ui/hooks/useTimer.test.js +43 -14
  203. package/dist/src/ui/hooks/useTimer.test.js.map +1 -1
  204. package/dist/src/ui/hooks/useToolScheduler.test.js +122 -39
  205. package/dist/src/ui/hooks/useToolScheduler.test.js.map +1 -1
  206. package/dist/src/ui/hooks/vim.test.js +251 -356
  207. package/dist/src/ui/hooks/vim.test.js.map +1 -1
  208. package/dist/src/ui/types.d.ts +2 -1
  209. package/dist/src/ui/types.js.map +1 -1
  210. package/dist/src/ui/utils/CodeColorizer.js +2 -1
  211. package/dist/src/ui/utils/CodeColorizer.js.map +1 -1
  212. package/dist/src/ui/utils/textOutput.d.ts +25 -0
  213. package/dist/src/ui/utils/textOutput.js +49 -0
  214. package/dist/src/ui/utils/textOutput.js.map +1 -0
  215. package/dist/src/ui/utils/textOutput.test.d.ts +6 -0
  216. package/dist/src/ui/utils/textOutput.test.js +79 -0
  217. package/dist/src/ui/utils/textOutput.test.js.map +1 -0
  218. package/dist/src/ui/utils/updateCheck.d.ts +7 -1
  219. package/dist/src/ui/utils/updateCheck.js +27 -26
  220. package/dist/src/ui/utils/updateCheck.js.map +1 -1
  221. package/dist/src/ui/utils/updateCheck.test.js +19 -49
  222. package/dist/src/ui/utils/updateCheck.test.js.map +1 -1
  223. package/dist/src/utils/envVarResolver.d.ts +2 -2
  224. package/dist/src/utils/envVarResolver.js +10 -7
  225. package/dist/src/utils/envVarResolver.js.map +1 -1
  226. package/dist/src/utils/handleAutoUpdate.js +9 -3
  227. package/dist/src/utils/handleAutoUpdate.js.map +1 -1
  228. package/dist/src/zed-integration/schema.d.ts +4 -4
  229. package/dist/src/zed-integration/zedIntegration.js +8 -13
  230. package/dist/src/zed-integration/zedIntegration.js.map +1 -1
  231. package/dist/tsconfig.tsbuildinfo +1 -1
  232. package/package.json +5 -4
  233. package/dist/google-gemini-cli-0.12.0-nightly.20251022.0542de95.tgz +0 -0
@@ -7,10 +7,11 @@ import { PassThrough } from 'node:stream';
7
7
  import { BACKSLASH_ENTER_DETECTION_WINDOW_MS, CHAR_CODE_ESC, KITTY_CTRL_C, KITTY_KEYCODE_BACKSPACE, KITTY_KEYCODE_ENTER, KITTY_KEYCODE_NUMPAD_ENTER, KITTY_KEYCODE_TAB, MAX_KITTY_SEQUENCE_LENGTH, KITTY_MODIFIER_BASE, KITTY_MODIFIER_EVENT_TYPES_OFFSET, MODIFIER_SHIFT_BIT, MODIFIER_ALT_BIT, MODIFIER_CTRL_BIT, } from '../utils/platformConstants.js';
8
8
  import { FOCUS_IN, FOCUS_OUT } from '../hooks/useFocus.js';
9
9
  const ESC = '\u001B';
10
- export const PASTE_MODE_PREFIX = `${ESC}[200~`;
11
- export const PASTE_MODE_SUFFIX = `${ESC}[201~`;
10
+ export const PASTE_MODE_START = `${ESC}[200~`;
11
+ export const PASTE_MODE_END = `${ESC}[201~`;
12
12
  export const DRAG_COMPLETION_TIMEOUT_MS = 100; // Broadcast full path after 100ms if no more input
13
13
  export const KITTY_SEQUENCE_TIMEOUT_MS = 50; // Flush incomplete kitty sequences after 50ms
14
+ export const PASTE_CODE_TIMEOUT_MS = 50; // Flush incomplete paste code after 50ms
14
15
  export const SINGLE_QUOTE = "'";
15
16
  export const DOUBLE_QUOTE = '"';
16
17
  const ALT_KEY_CHARACTER_MAP = {
@@ -41,169 +42,186 @@ const ALT_KEY_CHARACTER_MAP = {
41
42
  '\u00A5': 'y',
42
43
  '\u03A9': 'z',
43
44
  };
44
- const KeypressContext = createContext(undefined);
45
- export function useKeypressContext() {
46
- const context = useContext(KeypressContext);
47
- if (!context) {
48
- throw new Error('useKeypressContext must be used within a KeypressProvider');
49
- }
50
- return context;
45
+ /**
46
+ * Check if a buffer could potentially be a valid kitty sequence or its prefix.
47
+ */
48
+ function couldBeKittySequence(buffer) {
49
+ // Kitty sequences always start with ESC[.
50
+ if (buffer.length === 0)
51
+ return true;
52
+ if (buffer === ESC || buffer === `${ESC}[`)
53
+ return true;
54
+ if (!buffer.startsWith(`${ESC}[`))
55
+ return false;
56
+ // Check for known kitty sequence patterns:
57
+ // 1. ESC[<digit> - could be CSI-u or tilde-coded
58
+ // 2. ESC[1;<digit> - parameterized functional
59
+ // 3. ESC[<letter> - legacy functional keys
60
+ // 4. ESC[Z - reverse tab
61
+ const afterCSI = buffer.slice(2);
62
+ // Check if it starts with a digit (could be CSI-u or parameterized)
63
+ if (/^\d/.test(afterCSI))
64
+ return true;
65
+ // Check for known single-letter sequences
66
+ if (/^[ABCDHFPQRSZ]/.test(afterCSI))
67
+ return true;
68
+ // Check for 1; pattern (parameterized sequences)
69
+ if (/^1;\d/.test(afterCSI))
70
+ return true;
71
+ // Anything else starting with ESC[ that doesn't match our patterns
72
+ // is likely not a kitty sequence we handle
73
+ return false;
51
74
  }
52
- export function KeypressProvider({ children, kittyProtocolEnabled, config, debugKeystrokeLogging, }) {
53
- const { stdin, setRawMode } = useStdin();
54
- const subscribers = useRef(new Set()).current;
55
- const isDraggingRef = useRef(false);
56
- const dragBufferRef = useRef('');
57
- const draggingTimerRef = useRef(null);
58
- const subscribe = useCallback((handler) => {
59
- subscribers.add(handler);
60
- }, [subscribers]);
61
- const unsubscribe = useCallback((handler) => {
62
- subscribers.delete(handler);
63
- }, [subscribers]);
64
- useEffect(() => {
65
- const clearDraggingTimer = () => {
66
- if (draggingTimerRef.current) {
67
- clearTimeout(draggingTimerRef.current);
68
- draggingTimerRef.current = null;
69
- }
75
+ /**
76
+ * Parses a single complete kitty/parameterized/legacy sequence from the start
77
+ * of the buffer.
78
+ *
79
+ * This enables peel-and-continue parsing for batched input, allowing us to
80
+ * "peel off" one complete event when multiple sequences arrive in a single
81
+ * chunk, preventing buffer overflow and fragmentation.
82
+ *
83
+ * @param buffer - The input buffer string to parse.
84
+ * @returns The parsed Key and the number of characters consumed, or null if
85
+ * no complete sequence is found at the start of the buffer.
86
+ */
87
+ function parseKittyPrefix(buffer) {
88
+ // In older terminals ESC [ Z was used as Cursor Backward Tabulation (CBT)
89
+ // In newer terminals the same functionality of key combination for moving
90
+ // backward through focusable elements is Shift+Tab, hence we will
91
+ // map ESC [ Z to Shift+Tab
92
+ // 0) Reverse Tab (legacy): ESC [ Z
93
+ // Treat as Shift+Tab for UI purposes.
94
+ // Regex parts:
95
+ // ^ - start of buffer
96
+ // ESC [ - CSI introducer
97
+ // Z - legacy reverse tab
98
+ const revTabLegacy = new RegExp(`^${ESC}\\[Z`);
99
+ let m = buffer.match(revTabLegacy);
100
+ if (m) {
101
+ return {
102
+ key: {
103
+ name: 'tab',
104
+ ctrl: false,
105
+ meta: false,
106
+ shift: true,
107
+ paste: false,
108
+ sequence: buffer.slice(0, m[0].length),
109
+ kittyProtocol: true,
110
+ },
111
+ length: m[0].length,
70
112
  };
71
- const wasRaw = stdin.isRaw;
72
- if (wasRaw === false) {
73
- setRawMode(true);
113
+ }
114
+ // 1) Reverse Tab (parameterized): ESC [ 1 ; <mods> Z
115
+ // Parameterized reverse Tab: ESC [ 1 ; <mods> Z
116
+ const revTabParam = new RegExp(`^${ESC}\\[1;(\\d+)Z`);
117
+ m = buffer.match(revTabParam);
118
+ if (m) {
119
+ let mods = parseInt(m[1], 10);
120
+ if (mods >= KITTY_MODIFIER_EVENT_TYPES_OFFSET) {
121
+ mods -= KITTY_MODIFIER_EVENT_TYPES_OFFSET;
74
122
  }
75
- const keypressStream = new PassThrough();
76
- let usePassthrough = false;
77
- const nodeMajorVersion = parseInt(process.versions.node.split('.')[0], 10);
78
- if (nodeMajorVersion < 20 ||
79
- process.env['PASTE_WORKAROUND'] === '1' ||
80
- process.env['PASTE_WORKAROUND'] === 'true') {
81
- usePassthrough = true;
123
+ const bits = mods - KITTY_MODIFIER_BASE;
124
+ const alt = (bits & MODIFIER_ALT_BIT) === MODIFIER_ALT_BIT;
125
+ const ctrl = (bits & MODIFIER_CTRL_BIT) === MODIFIER_CTRL_BIT;
126
+ return {
127
+ key: {
128
+ name: 'tab',
129
+ ctrl,
130
+ meta: alt,
131
+ // Reverse tab implies Shift behavior; force shift regardless of mods
132
+ shift: true,
133
+ paste: false,
134
+ sequence: buffer.slice(0, m[0].length),
135
+ kittyProtocol: true,
136
+ },
137
+ length: m[0].length,
138
+ };
139
+ }
140
+ // 2) Parameterized functional: ESC [ 1 ; <mods> (A|B|C|D|H|F|P|Q|R|S)
141
+ // 2) Parameterized functional: ESC [ 1 ; <mods> (A|B|C|D|H|F|P|Q|R|S)
142
+ // Arrows, Home/End, F1–F4 with modifiers encoded in <mods>.
143
+ const arrowPrefix = new RegExp(`^${ESC}\\[1;(\\d+)([ABCDHFPQSR])`);
144
+ m = buffer.match(arrowPrefix);
145
+ if (m) {
146
+ let mods = parseInt(m[1], 10);
147
+ if (mods >= KITTY_MODIFIER_EVENT_TYPES_OFFSET) {
148
+ mods -= KITTY_MODIFIER_EVENT_TYPES_OFFSET;
82
149
  }
83
- let isPaste = false;
84
- let pasteBuffer = Buffer.alloc(0);
85
- let kittySequenceBuffer = '';
86
- let kittySequenceTimeout = null;
87
- let backslashTimeout = null;
88
- let waitingForEnterAfterBackslash = false;
89
- // Check if a buffer could potentially be a valid kitty sequence or its prefix
90
- const couldBeKittySequence = (buffer) => {
91
- // Kitty sequences always start with ESC[.
92
- if (buffer.length === 0)
93
- return true;
94
- if (buffer === ESC || buffer === `${ESC}[`)
95
- return true;
96
- if (!buffer.startsWith(`${ESC}[`))
97
- return false;
98
- // Check for known kitty sequence patterns:
99
- // 1. ESC[<digit> - could be CSI-u or tilde-coded
100
- // 2. ESC[1;<digit> - parameterized functional
101
- // 3. ESC[<letter> - legacy functional keys
102
- // 4. ESC[Z - reverse tab
103
- const afterCSI = buffer.slice(2);
104
- // Check if it starts with a digit (could be CSI-u or parameterized)
105
- if (/^\d/.test(afterCSI))
106
- return true;
107
- // Check for known single-letter sequences
108
- if (/^[ABCDHFPQRSZ]/.test(afterCSI))
109
- return true;
110
- // Check for 1; pattern (parameterized sequences)
111
- if (/^1;\d/.test(afterCSI))
112
- return true;
113
- // Anything else starting with ESC[ that doesn't match our patterns
114
- // is likely not a kitty sequence we handle
115
- return false;
150
+ const bits = mods - KITTY_MODIFIER_BASE;
151
+ const shift = (bits & MODIFIER_SHIFT_BIT) === MODIFIER_SHIFT_BIT;
152
+ const alt = (bits & MODIFIER_ALT_BIT) === MODIFIER_ALT_BIT;
153
+ const ctrl = (bits & MODIFIER_CTRL_BIT) === MODIFIER_CTRL_BIT;
154
+ const sym = m[2];
155
+ const symbolToName = {
156
+ A: 'up',
157
+ B: 'down',
158
+ C: 'right',
159
+ D: 'left',
160
+ H: 'home',
161
+ F: 'end',
162
+ P: 'f1',
163
+ Q: 'f2',
164
+ R: 'f3',
165
+ S: 'f4',
116
166
  };
117
- // Parse a single complete kitty sequence from the start (prefix) of the
118
- // buffer and return both the Key and the number of characters consumed.
119
- // This lets us "peel off" one complete event when multiple sequences arrive
120
- // in a single chunk, preventing buffer overflow and fragmentation.
121
- // Parse a single complete kitty/parameterized/legacy sequence from the start
122
- // of the buffer and return both the parsed Key and the number of characters
123
- // consumed. This enables peel-and-continue parsing for batched input.
124
- const parseKittyPrefix = (buffer) => {
125
- // In older terminals ESC [ Z was used as Cursor Backward Tabulation (CBT)
126
- // In newer terminals the same functionality of key combination for moving
127
- // backward through focusable elements is Shift+Tab, hence we will
128
- // map ESC [ Z to Shift+Tab
129
- // 0) Reverse Tab (legacy): ESC [ Z
130
- // Treat as Shift+Tab for UI purposes.
131
- // Regex parts:
132
- // ^ - start of buffer
133
- // ESC [ - CSI introducer
134
- // Z - legacy reverse tab
135
- const revTabLegacy = new RegExp(`^${ESC}\\[Z`);
136
- let m = buffer.match(revTabLegacy);
137
- if (m) {
138
- return {
139
- key: {
140
- name: 'tab',
141
- ctrl: false,
142
- meta: false,
143
- shift: true,
144
- paste: false,
145
- sequence: buffer.slice(0, m[0].length),
146
- kittyProtocol: true,
147
- },
148
- length: m[0].length,
149
- };
150
- }
151
- // 1) Reverse Tab (parameterized): ESC [ 1 ; <mods> Z
152
- // Parameterized reverse Tab: ESC [ 1 ; <mods> Z
153
- const revTabParam = new RegExp(`^${ESC}\\[1;(\\d+)Z`);
154
- m = buffer.match(revTabParam);
155
- if (m) {
156
- let mods = parseInt(m[1], 10);
157
- if (mods >= KITTY_MODIFIER_EVENT_TYPES_OFFSET) {
158
- mods -= KITTY_MODIFIER_EVENT_TYPES_OFFSET;
159
- }
160
- const bits = mods - KITTY_MODIFIER_BASE;
161
- const alt = (bits & MODIFIER_ALT_BIT) === MODIFIER_ALT_BIT;
162
- const ctrl = (bits & MODIFIER_CTRL_BIT) === MODIFIER_CTRL_BIT;
163
- return {
164
- key: {
165
- name: 'tab',
166
- ctrl,
167
- meta: alt,
168
- // Reverse tab implies Shift behavior; force shift regardless of mods
169
- shift: true,
170
- paste: false,
171
- sequence: buffer.slice(0, m[0].length),
172
- kittyProtocol: true,
173
- },
174
- length: m[0].length,
175
- };
176
- }
177
- // 2) Parameterized functional: ESC [ 1 ; <mods> (A|B|C|D|H|F|P|Q|R|S)
178
- // 2) Parameterized functional: ESC [ 1 ; <mods> (A|B|C|D|H|F|P|Q|R|S)
179
- // Arrows, Home/End, F1–F4 with modifiers encoded in <mods>.
180
- const arrowPrefix = new RegExp(`^${ESC}\\[1;(\\d+)([ABCDHFPQSR])`);
181
- m = buffer.match(arrowPrefix);
182
- if (m) {
183
- let mods = parseInt(m[1], 10);
184
- if (mods >= KITTY_MODIFIER_EVENT_TYPES_OFFSET) {
185
- mods -= KITTY_MODIFIER_EVENT_TYPES_OFFSET;
186
- }
187
- const bits = mods - KITTY_MODIFIER_BASE;
188
- const shift = (bits & MODIFIER_SHIFT_BIT) === MODIFIER_SHIFT_BIT;
189
- const alt = (bits & MODIFIER_ALT_BIT) === MODIFIER_ALT_BIT;
190
- const ctrl = (bits & MODIFIER_CTRL_BIT) === MODIFIER_CTRL_BIT;
191
- const sym = m[2];
192
- const symbolToName = {
193
- A: 'up',
194
- B: 'down',
195
- C: 'right',
196
- D: 'left',
197
- H: 'home',
198
- F: 'end',
199
- P: 'f1',
200
- Q: 'f2',
201
- R: 'f3',
202
- S: 'f4',
203
- };
204
- const name = symbolToName[sym] || '';
205
- if (!name)
206
- return null;
167
+ const name = symbolToName[sym] || '';
168
+ if (!name)
169
+ return null;
170
+ return {
171
+ key: {
172
+ name,
173
+ ctrl,
174
+ meta: alt,
175
+ shift,
176
+ paste: false,
177
+ sequence: buffer.slice(0, m[0].length),
178
+ kittyProtocol: true,
179
+ },
180
+ length: m[0].length,
181
+ };
182
+ }
183
+ // 3) CSI-u form: ESC [ <code> ; <mods> (u|~)
184
+ // 3) CSI-u and tilde-coded functional keys: ESC [ <code> ; <mods> (u|~)
185
+ // 'u' terminator: Kitty CSI-u; '~' terminator: tilde-coded function keys.
186
+ const csiUPrefix = new RegExp(`^${ESC}\\[(\\d+)(;(\\d+))?([u~])`);
187
+ m = buffer.match(csiUPrefix);
188
+ if (m) {
189
+ const keyCode = parseInt(m[1], 10);
190
+ let modifiers = m[3] ? parseInt(m[3], 10) : KITTY_MODIFIER_BASE;
191
+ if (modifiers >= KITTY_MODIFIER_EVENT_TYPES_OFFSET) {
192
+ modifiers -= KITTY_MODIFIER_EVENT_TYPES_OFFSET;
193
+ }
194
+ const modifierBits = modifiers - KITTY_MODIFIER_BASE;
195
+ const shift = (modifierBits & MODIFIER_SHIFT_BIT) === MODIFIER_SHIFT_BIT;
196
+ const alt = (modifierBits & MODIFIER_ALT_BIT) === MODIFIER_ALT_BIT;
197
+ const ctrl = (modifierBits & MODIFIER_CTRL_BIT) === MODIFIER_CTRL_BIT;
198
+ const terminator = m[4];
199
+ // Tilde-coded functional keys (Delete, Insert, PageUp/Down, Home/End)
200
+ if (terminator === '~') {
201
+ let name = null;
202
+ switch (keyCode) {
203
+ case 1:
204
+ name = 'home';
205
+ break;
206
+ case 2:
207
+ name = 'insert';
208
+ break;
209
+ case 3:
210
+ name = 'delete';
211
+ break;
212
+ case 4:
213
+ name = 'end';
214
+ break;
215
+ case 5:
216
+ name = 'pageup';
217
+ break;
218
+ case 6:
219
+ name = 'pagedown';
220
+ break;
221
+ default:
222
+ break;
223
+ }
224
+ if (name) {
207
225
  return {
208
226
  key: {
209
227
  name,
@@ -217,136 +235,207 @@ export function KeypressProvider({ children, kittyProtocolEnabled, config, debug
217
235
  length: m[0].length,
218
236
  };
219
237
  }
220
- // 3) CSI-u form: ESC [ <code> ; <mods> (u|~)
221
- // 3) CSI-u and tilde-coded functional keys: ESC [ <code> ; <mods> (u|~)
222
- // 'u' terminator: Kitty CSI-u; '~' terminator: tilde-coded function keys.
223
- const csiUPrefix = new RegExp(`^${ESC}\\[(\\d+)(;(\\d+))?([u~])`);
224
- m = buffer.match(csiUPrefix);
225
- if (m) {
226
- const keyCode = parseInt(m[1], 10);
227
- let modifiers = m[3] ? parseInt(m[3], 10) : KITTY_MODIFIER_BASE;
228
- if (modifiers >= KITTY_MODIFIER_EVENT_TYPES_OFFSET) {
229
- modifiers -= KITTY_MODIFIER_EVENT_TYPES_OFFSET;
230
- }
231
- const modifierBits = modifiers - KITTY_MODIFIER_BASE;
232
- const shift = (modifierBits & MODIFIER_SHIFT_BIT) === MODIFIER_SHIFT_BIT;
233
- const alt = (modifierBits & MODIFIER_ALT_BIT) === MODIFIER_ALT_BIT;
234
- const ctrl = (modifierBits & MODIFIER_CTRL_BIT) === MODIFIER_CTRL_BIT;
235
- const terminator = m[4];
236
- // Tilde-coded functional keys (Delete, Insert, PageUp/Down, Home/End)
237
- if (terminator === '~') {
238
- let name = null;
239
- switch (keyCode) {
240
- case 1:
241
- name = 'home';
242
- break;
243
- case 2:
244
- name = 'insert';
245
- break;
246
- case 3:
247
- name = 'delete';
248
- break;
249
- case 4:
250
- name = 'end';
251
- break;
252
- case 5:
253
- name = 'pageup';
254
- break;
255
- case 6:
256
- name = 'pagedown';
257
- break;
258
- default:
259
- break;
260
- }
261
- if (name) {
262
- return {
263
- key: {
264
- name,
265
- ctrl,
266
- meta: alt,
267
- shift,
268
- paste: false,
269
- sequence: buffer.slice(0, m[0].length),
270
- kittyProtocol: true,
271
- },
272
- length: m[0].length,
273
- };
274
- }
275
- }
276
- const kittyKeyCodeToName = {
277
- [CHAR_CODE_ESC]: 'escape',
278
- [KITTY_KEYCODE_TAB]: 'tab',
279
- [KITTY_KEYCODE_BACKSPACE]: 'backspace',
280
- [KITTY_KEYCODE_ENTER]: 'return',
281
- [KITTY_KEYCODE_NUMPAD_ENTER]: 'return',
282
- };
283
- const name = kittyKeyCodeToName[keyCode];
284
- if (name) {
285
- return {
286
- key: {
287
- name,
288
- ctrl,
289
- meta: alt,
290
- shift,
291
- paste: false,
292
- sequence: buffer.slice(0, m[0].length),
293
- kittyProtocol: true,
294
- },
295
- length: m[0].length,
296
- };
297
- }
298
- // Ctrl+letters and Alt+letters
299
- if ((ctrl || alt) &&
300
- keyCode >= 'a'.charCodeAt(0) &&
301
- keyCode <= 'z'.charCodeAt(0)) {
302
- const letter = String.fromCharCode(keyCode);
303
- return {
304
- key: {
305
- name: letter,
306
- ctrl,
307
- meta: alt,
308
- shift,
309
- paste: false,
310
- sequence: buffer.slice(0, m[0].length),
311
- kittyProtocol: true,
312
- },
313
- length: m[0].length,
314
- };
238
+ }
239
+ const kittyKeyCodeToName = {
240
+ [CHAR_CODE_ESC]: 'escape',
241
+ [KITTY_KEYCODE_TAB]: 'tab',
242
+ [KITTY_KEYCODE_BACKSPACE]: 'backspace',
243
+ [KITTY_KEYCODE_ENTER]: 'return',
244
+ [KITTY_KEYCODE_NUMPAD_ENTER]: 'return',
245
+ };
246
+ const name = kittyKeyCodeToName[keyCode];
247
+ if (name) {
248
+ return {
249
+ key: {
250
+ name,
251
+ ctrl,
252
+ meta: alt,
253
+ shift,
254
+ paste: false,
255
+ sequence: buffer.slice(0, m[0].length),
256
+ kittyProtocol: true,
257
+ },
258
+ length: m[0].length,
259
+ };
260
+ }
261
+ // Ctrl+letters and Alt+letters
262
+ if ((ctrl || alt) &&
263
+ keyCode >= 'a'.charCodeAt(0) &&
264
+ keyCode <= 'z'.charCodeAt(0)) {
265
+ const letter = String.fromCharCode(keyCode);
266
+ return {
267
+ key: {
268
+ name: letter,
269
+ ctrl,
270
+ meta: alt,
271
+ shift,
272
+ paste: false,
273
+ sequence: buffer.slice(0, m[0].length),
274
+ kittyProtocol: true,
275
+ },
276
+ length: m[0].length,
277
+ };
278
+ }
279
+ }
280
+ // 4) Legacy function keys (no parameters): ESC [ (A|B|C|D|H|F)
281
+ // Arrows + Home/End without modifiers.
282
+ const legacyFuncKey = new RegExp(`^${ESC}\\[([ABCDHF])`);
283
+ m = buffer.match(legacyFuncKey);
284
+ if (m) {
285
+ const sym = m[1];
286
+ const nameMap = {
287
+ A: 'up',
288
+ B: 'down',
289
+ C: 'right',
290
+ D: 'left',
291
+ H: 'home',
292
+ F: 'end',
293
+ };
294
+ const name = nameMap[sym];
295
+ return {
296
+ key: {
297
+ name,
298
+ ctrl: false,
299
+ meta: false,
300
+ shift: false,
301
+ paste: false,
302
+ sequence: buffer.slice(0, m[0].length),
303
+ kittyProtocol: true,
304
+ },
305
+ length: m[0].length,
306
+ };
307
+ }
308
+ return null;
309
+ }
310
+ /**
311
+ * Returns the first index before which we are certain there is no paste marker.
312
+ */
313
+ function earliestPossiblePasteMarker(data) {
314
+ // Check data for full start-paste or end-paste markers.
315
+ const startIndex = data.indexOf(PASTE_MODE_START);
316
+ const endIndex = data.indexOf(PASTE_MODE_END);
317
+ if (startIndex !== -1 && endIndex !== -1) {
318
+ return Math.min(startIndex, endIndex);
319
+ }
320
+ else if (startIndex !== -1) {
321
+ return startIndex;
322
+ }
323
+ else if (endIndex !== -1) {
324
+ return endIndex;
325
+ }
326
+ // data contains no full start-paste or end-paste.
327
+ // Check if data ends with a prefix of start-paste or end-paste.
328
+ const codeLength = PASTE_MODE_START.length;
329
+ for (let i = Math.min(data.length, codeLength - 1); i > 0; i--) {
330
+ const candidate = data.slice(data.length - i);
331
+ if (PASTE_MODE_START.indexOf(candidate) === 0 ||
332
+ PASTE_MODE_END.indexOf(candidate) === 0) {
333
+ return data.length - i;
334
+ }
335
+ }
336
+ return data.length;
337
+ }
338
+ /**
339
+ * A generator that takes in data chunks and spits out paste-start and
340
+ * paste-end keypresses. All non-paste marker data is passed to passthrough.
341
+ */
342
+ function* pasteMarkerParser(passthrough, keypressHandler) {
343
+ while (true) {
344
+ let data = yield;
345
+ if (data.length === 0) {
346
+ continue; // we timed out
347
+ }
348
+ while (true) {
349
+ const index = earliestPossiblePasteMarker(data);
350
+ if (index === data.length) {
351
+ // no possible paste markers were found
352
+ passthrough.write(data);
353
+ break;
354
+ }
355
+ if (index > 0) {
356
+ // snip off and send the part that doesn't have a paste marker
357
+ passthrough.write(data.slice(0, index));
358
+ data = data.slice(index);
359
+ }
360
+ // data starts with a possible paste marker
361
+ const codeLength = PASTE_MODE_START.length;
362
+ if (data.length < codeLength) {
363
+ // we have a prefix. Concat the next data and try again.
364
+ const newData = yield;
365
+ if (newData.length === 0) {
366
+ // we timed out. Just dump what we have and start over.
367
+ passthrough.write(data);
368
+ break;
315
369
  }
370
+ data += newData;
316
371
  }
317
- // 4) Legacy function keys (no parameters): ESC [ (A|B|C|D|H|F)
318
- // Arrows + Home/End without modifiers.
319
- const legacyFuncKey = new RegExp(`^${ESC}\\[([ABCDHF])`);
320
- m = buffer.match(legacyFuncKey);
321
- if (m) {
322
- const sym = m[1];
323
- const nameMap = {
324
- A: 'up',
325
- B: 'down',
326
- C: 'right',
327
- D: 'left',
328
- H: 'home',
329
- F: 'end',
330
- };
331
- const name = nameMap[sym];
332
- return {
333
- key: {
334
- name,
335
- ctrl: false,
336
- meta: false,
337
- shift: false,
338
- paste: false,
339
- sequence: buffer.slice(0, m[0].length),
340
- kittyProtocol: true,
341
- },
342
- length: m[0].length,
343
- };
372
+ else if (data.startsWith(PASTE_MODE_START)) {
373
+ keypressHandler(undefined, {
374
+ name: 'paste-start',
375
+ ctrl: false,
376
+ meta: false,
377
+ shift: false,
378
+ paste: false,
379
+ sequence: '',
380
+ });
381
+ data = data.slice(PASTE_MODE_START.length);
344
382
  }
345
- return null;
346
- };
347
- const broadcast = (key) => {
348
- for (const handler of subscribers) {
349
- handler(key);
383
+ else if (data.startsWith(PASTE_MODE_END)) {
384
+ keypressHandler(undefined, {
385
+ name: 'paste-end',
386
+ ctrl: false,
387
+ meta: false,
388
+ shift: false,
389
+ paste: false,
390
+ sequence: '',
391
+ });
392
+ data = data.slice(PASTE_MODE_END.length);
393
+ }
394
+ else {
395
+ // This should never happen.
396
+ passthrough.write(data);
397
+ break;
398
+ }
399
+ }
400
+ }
401
+ }
402
+ const KeypressContext = createContext(undefined);
403
+ export function useKeypressContext() {
404
+ const context = useContext(KeypressContext);
405
+ if (!context) {
406
+ throw new Error('useKeypressContext must be used within a KeypressProvider');
407
+ }
408
+ return context;
409
+ }
410
+ function shouldUsePassthrough() {
411
+ return process.env['PASTE_WORKAROUND'] !== 'false';
412
+ }
413
+ export function KeypressProvider({ children, kittyProtocolEnabled, config, debugKeystrokeLogging, }) {
414
+ const { stdin, setRawMode } = useStdin();
415
+ const subscribers = useRef(new Set()).current;
416
+ const subscribe = useCallback((handler) => subscribers.add(handler), [subscribers]);
417
+ const unsubscribe = useCallback((handler) => subscribers.delete(handler), [subscribers]);
418
+ const broadcast = useCallback((key) => subscribers.forEach((handler) => handler(key)), [subscribers]);
419
+ useEffect(() => {
420
+ const wasRaw = stdin.isRaw;
421
+ if (wasRaw === false) {
422
+ setRawMode(true);
423
+ }
424
+ const keypressStream = shouldUsePassthrough() ? new PassThrough() : null;
425
+ // If non-null that means we are in paste mode
426
+ let pasteBuffer = null;
427
+ // Used to turn "\" quickly followed by a "enter" into a shift enter
428
+ let backslashTimeout = null;
429
+ // Buffers incomplete Kitty sequences and timer to flush it
430
+ let kittySequenceBuffer = '';
431
+ let kittySequenceTimeout = null;
432
+ // Used to detect filename drag-and-drops.
433
+ let dragBuffer = '';
434
+ let draggingTimer = null;
435
+ const clearDraggingTimer = () => {
436
+ if (draggingTimer) {
437
+ clearTimeout(draggingTimer);
438
+ draggingTimer = null;
350
439
  }
351
440
  };
352
441
  const flushKittyBufferOnInterrupt = (reason) => {
@@ -376,36 +465,36 @@ export function KeypressProvider({ children, kittyProtocolEnabled, config, debug
376
465
  }
377
466
  if (key.name === 'paste-start') {
378
467
  flushKittyBufferOnInterrupt('paste start');
379
- isPaste = true;
468
+ pasteBuffer = Buffer.alloc(0);
380
469
  return;
381
470
  }
382
471
  if (key.name === 'paste-end') {
383
- isPaste = false;
384
- broadcast({
385
- name: '',
386
- ctrl: false,
387
- meta: false,
388
- shift: false,
389
- paste: true,
390
- sequence: pasteBuffer.toString(),
391
- });
392
- pasteBuffer = Buffer.alloc(0);
472
+ if (pasteBuffer !== null) {
473
+ broadcast({
474
+ name: '',
475
+ ctrl: false,
476
+ meta: false,
477
+ shift: false,
478
+ paste: true,
479
+ sequence: pasteBuffer.toString(),
480
+ });
481
+ }
482
+ pasteBuffer = null;
393
483
  return;
394
484
  }
395
- if (isPaste) {
485
+ if (pasteBuffer !== null) {
396
486
  pasteBuffer = Buffer.concat([pasteBuffer, Buffer.from(key.sequence)]);
397
487
  return;
398
488
  }
399
489
  if (key.sequence === SINGLE_QUOTE ||
400
490
  key.sequence === DOUBLE_QUOTE ||
401
- isDraggingRef.current) {
402
- isDraggingRef.current = true;
403
- dragBufferRef.current += key.sequence;
491
+ draggingTimer !== null) {
492
+ dragBuffer += key.sequence;
404
493
  clearDraggingTimer();
405
- draggingTimerRef.current = setTimeout(() => {
406
- isDraggingRef.current = false;
407
- const seq = dragBufferRef.current;
408
- dragBufferRef.current = '';
494
+ draggingTimer = setTimeout(() => {
495
+ draggingTimer = null;
496
+ const seq = dragBuffer;
497
+ dragBuffer = '';
409
498
  if (seq) {
410
499
  broadcast({ ...key, name: '', paste: true, sequence: seq });
411
500
  }
@@ -419,17 +508,14 @@ export function KeypressProvider({ children, kittyProtocolEnabled, config, debug
419
508
  ctrl: false,
420
509
  meta: true,
421
510
  shift: false,
422
- paste: isPaste,
511
+ paste: pasteBuffer !== null,
423
512
  sequence: key.sequence,
424
513
  });
425
514
  return;
426
515
  }
427
- if (key.name === 'return' && waitingForEnterAfterBackslash) {
428
- if (backslashTimeout) {
429
- clearTimeout(backslashTimeout);
430
- backslashTimeout = null;
431
- }
432
- waitingForEnterAfterBackslash = false;
516
+ if (key.name === 'return' && backslashTimeout !== null) {
517
+ clearTimeout(backslashTimeout);
518
+ backslashTimeout = null;
433
519
  broadcast({
434
520
  ...key,
435
521
  shift: true,
@@ -439,20 +525,15 @@ export function KeypressProvider({ children, kittyProtocolEnabled, config, debug
439
525
  }
440
526
  if (key.sequence === '\\' && !key.name) {
441
527
  // Corrected escaping for backslash
442
- waitingForEnterAfterBackslash = true;
443
528
  backslashTimeout = setTimeout(() => {
444
- waitingForEnterAfterBackslash = false;
445
529
  backslashTimeout = null;
446
530
  broadcast(key);
447
531
  }, BACKSLASH_ENTER_DETECTION_WINDOW_MS);
448
532
  return;
449
533
  }
450
- if (waitingForEnterAfterBackslash && key.name !== 'return') {
451
- if (backslashTimeout) {
452
- clearTimeout(backslashTimeout);
453
- backslashTimeout = null;
454
- }
455
- waitingForEnterAfterBackslash = false;
534
+ if (backslashTimeout !== null && key.name !== 'return') {
535
+ clearTimeout(backslashTimeout);
536
+ backslashTimeout = null;
456
537
  broadcast({
457
538
  name: '',
458
539
  sequence: '\\',
@@ -501,8 +582,8 @@ export function KeypressProvider({ children, kittyProtocolEnabled, config, debug
501
582
  // Check if this could start a kitty sequence
502
583
  const startsWithEsc = key.sequence.startsWith(ESC);
503
584
  const isExcluded = [
504
- PASTE_MODE_PREFIX,
505
- PASTE_MODE_SUFFIX,
585
+ PASTE_MODE_START,
586
+ PASTE_MODE_END,
506
587
  FOCUS_IN,
507
588
  FOCUS_OUT,
508
589
  ].some((prefix) => key.sequence.startsWith(prefix));
@@ -614,74 +695,40 @@ export function KeypressProvider({ children, kittyProtocolEnabled, config, debug
614
695
  if (key.name === 'return' && key.sequence === `${ESC}\r`) {
615
696
  key.meta = true;
616
697
  }
617
- broadcast({ ...key, paste: isPaste });
618
- };
619
- const handleRawKeypress = (data) => {
620
- const pasteModePrefixBuffer = Buffer.from(PASTE_MODE_PREFIX);
621
- const pasteModeSuffixBuffer = Buffer.from(PASTE_MODE_SUFFIX);
622
- let pos = 0;
623
- while (pos < data.length) {
624
- const prefixPos = data.indexOf(pasteModePrefixBuffer, pos);
625
- const suffixPos = data.indexOf(pasteModeSuffixBuffer, pos);
626
- const isPrefixNext = prefixPos !== -1 && (suffixPos === -1 || prefixPos < suffixPos);
627
- const isSuffixNext = suffixPos !== -1 && (prefixPos === -1 || suffixPos < prefixPos);
628
- let nextMarkerPos = -1;
629
- let markerLength = 0;
630
- if (isPrefixNext) {
631
- nextMarkerPos = prefixPos;
632
- }
633
- else if (isSuffixNext) {
634
- nextMarkerPos = suffixPos;
635
- }
636
- markerLength = pasteModeSuffixBuffer.length;
637
- if (nextMarkerPos === -1) {
638
- keypressStream.write(data.slice(pos));
639
- return;
640
- }
641
- const nextData = data.slice(pos, nextMarkerPos);
642
- if (nextData.length > 0) {
643
- keypressStream.write(nextData);
644
- }
645
- const createPasteKeyEvent = (name) => ({
646
- name,
647
- ctrl: false,
648
- meta: false,
649
- shift: false,
650
- paste: false,
651
- sequence: '',
652
- });
653
- if (isPrefixNext) {
654
- handleKeypress(undefined, createPasteKeyEvent('paste-start'));
655
- }
656
- else if (isSuffixNext) {
657
- handleKeypress(undefined, createPasteKeyEvent('paste-end'));
658
- }
659
- pos = nextMarkerPos + markerLength;
660
- }
698
+ broadcast({ ...key, paste: pasteBuffer !== null });
661
699
  };
700
+ let cleanup = () => { };
662
701
  let rl;
663
- if (usePassthrough) {
702
+ if (keypressStream !== null) {
664
703
  rl = readline.createInterface({
665
704
  input: keypressStream,
666
705
  escapeCodeTimeout: 0,
667
706
  });
668
707
  readline.emitKeypressEvents(keypressStream, rl);
708
+ const parser = pasteMarkerParser(keypressStream, handleKeypress);
709
+ parser.next(); // prime the generator so it starts listening.
710
+ let timeoutId;
711
+ const handleRawKeypress = (data) => {
712
+ clearTimeout(timeoutId);
713
+ parser.next(data);
714
+ timeoutId = setTimeout(() => parser.next(''), PASTE_CODE_TIMEOUT_MS);
715
+ };
669
716
  keypressStream.on('keypress', handleKeypress);
717
+ process.stdin.setEncoding('utf8'); // so handleRawKeypress gets strings
670
718
  stdin.on('data', handleRawKeypress);
719
+ cleanup = () => {
720
+ keypressStream.removeListener('keypress', handleKeypress);
721
+ stdin.removeListener('data', handleRawKeypress);
722
+ };
671
723
  }
672
724
  else {
673
725
  rl = readline.createInterface({ input: stdin, escapeCodeTimeout: 0 });
674
726
  readline.emitKeypressEvents(stdin, rl);
675
727
  stdin.on('keypress', handleKeypress);
728
+ cleanup = () => stdin.removeListener('keypress', handleKeypress);
676
729
  }
677
730
  return () => {
678
- if (usePassthrough) {
679
- keypressStream.removeListener('keypress', handleKeypress);
680
- stdin.removeListener('data', handleRawKeypress);
681
- }
682
- else {
683
- stdin.removeListener('keypress', handleKeypress);
684
- }
731
+ cleanup();
685
732
  rl.close();
686
733
  // Restore the terminal to its original state.
687
734
  if (wasRaw === false) {
@@ -708,7 +755,7 @@ export function KeypressProvider({ children, kittyProtocolEnabled, config, debug
708
755
  kittySequenceBuffer = '';
709
756
  }
710
757
  // Flush any pending paste data to avoid data loss on exit.
711
- if (isPaste) {
758
+ if (pasteBuffer !== null) {
712
759
  broadcast({
713
760
  name: '',
714
761
  ctrl: false,
@@ -717,23 +764,19 @@ export function KeypressProvider({ children, kittyProtocolEnabled, config, debug
717
764
  paste: true,
718
765
  sequence: pasteBuffer.toString(),
719
766
  });
720
- pasteBuffer = Buffer.alloc(0);
721
- }
722
- if (draggingTimerRef.current) {
723
- clearTimeout(draggingTimerRef.current);
724
- draggingTimerRef.current = null;
767
+ pasteBuffer = null;
725
768
  }
726
- if (isDraggingRef.current && dragBufferRef.current) {
769
+ clearDraggingTimer();
770
+ if (dragBuffer) {
727
771
  broadcast({
728
772
  name: '',
729
773
  ctrl: false,
730
774
  meta: false,
731
775
  shift: false,
732
776
  paste: true,
733
- sequence: dragBufferRef.current,
777
+ sequence: dragBuffer,
734
778
  });
735
- isDraggingRef.current = false;
736
- dragBufferRef.current = '';
779
+ dragBuffer = '';
737
780
  }
738
781
  };
739
782
  }, [
@@ -741,8 +784,8 @@ export function KeypressProvider({ children, kittyProtocolEnabled, config, debug
741
784
  setRawMode,
742
785
  kittyProtocolEnabled,
743
786
  config,
744
- subscribers,
745
787
  debugKeystrokeLogging,
788
+ broadcast,
746
789
  ]);
747
790
  return (_jsx(KeypressContext.Provider, { value: { subscribe, unsubscribe }, children: children }));
748
791
  }