@machina.ai/cell-cli 1.11.0-rc1 → 1.13.0-rc2

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 (538) hide show
  1. package/dist/package.json +16 -10
  2. package/dist/src/commands/extensions/disable.d.ts +1 -1
  3. package/dist/src/commands/extensions/disable.js +15 -7
  4. package/dist/src/commands/extensions/disable.js.map +1 -1
  5. package/dist/src/commands/extensions/enable.d.ts +1 -1
  6. package/dist/src/commands/extensions/enable.js +15 -7
  7. package/dist/src/commands/extensions/enable.js.map +1 -1
  8. package/dist/src/commands/extensions/install.js +14 -3
  9. package/dist/src/commands/extensions/install.js.map +1 -1
  10. package/dist/src/commands/extensions/install.test.js +39 -19
  11. package/dist/src/commands/extensions/install.test.js.map +1 -1
  12. package/dist/src/commands/extensions/link.js +14 -3
  13. package/dist/src/commands/extensions/link.js.map +1 -1
  14. package/dist/src/commands/extensions/list.js +13 -4
  15. package/dist/src/commands/extensions/list.js.map +1 -1
  16. package/dist/src/commands/extensions/uninstall.js +13 -2
  17. package/dist/src/commands/extensions/uninstall.js.map +1 -1
  18. package/dist/src/commands/extensions/update.js +18 -13
  19. package/dist/src/commands/extensions/update.js.map +1 -1
  20. package/dist/src/commands/extensions/validate.d.ts +12 -0
  21. package/dist/src/commands/extensions/validate.js +83 -0
  22. package/dist/src/commands/extensions/validate.js.map +1 -0
  23. package/dist/src/commands/extensions/validate.test.js +93 -0
  24. package/dist/src/commands/extensions/validate.test.js.map +1 -0
  25. package/dist/src/commands/extensions.js +3 -0
  26. package/dist/src/commands/extensions.js.map +1 -1
  27. package/dist/src/commands/mcp/add.test.js +3 -0
  28. package/dist/src/commands/mcp/add.test.js.map +1 -1
  29. package/dist/src/commands/mcp/list.js +10 -3
  30. package/dist/src/commands/mcp/list.js.map +1 -1
  31. package/dist/src/commands/mcp/list.test.js +37 -27
  32. package/dist/src/commands/mcp/list.test.js.map +1 -1
  33. package/dist/src/config/auth.js +0 -5
  34. package/dist/src/config/auth.js.map +1 -1
  35. package/dist/src/config/config.d.ts +6 -3
  36. package/dist/src/config/config.js +65 -80
  37. package/dist/src/config/config.js.map +1 -1
  38. package/dist/src/config/config.test.js +235 -212
  39. package/dist/src/config/config.test.js.map +1 -1
  40. package/dist/src/config/extension-manager.d.ts +63 -0
  41. package/dist/src/config/extension-manager.js +450 -0
  42. package/dist/src/config/extension-manager.js.map +1 -0
  43. package/dist/src/config/extension.d.ts +4 -51
  44. package/dist/src/config/extension.js +1 -535
  45. package/dist/src/config/extension.js.map +1 -1
  46. package/dist/src/config/extension.test.js +525 -201
  47. package/dist/src/config/extension.test.js.map +1 -1
  48. package/dist/src/config/extensions/consent.d.ts +38 -0
  49. package/dist/src/config/extensions/consent.js +123 -0
  50. package/dist/src/config/extensions/consent.js.map +1 -0
  51. package/dist/src/config/extensions/extensionEnablement.d.ts +1 -1
  52. package/dist/src/config/extensions/extensionEnablement.js +4 -3
  53. package/dist/src/config/extensions/extensionEnablement.js.map +1 -1
  54. package/dist/src/config/extensions/extensionEnablement.test.js +10 -10
  55. package/dist/src/config/extensions/extensionEnablement.test.js.map +1 -1
  56. package/dist/src/config/extensions/extensionSettings.d.ts +15 -0
  57. package/dist/src/config/extensions/extensionSettings.js +113 -0
  58. package/dist/src/config/extensions/extensionSettings.js.map +1 -0
  59. package/dist/src/config/extensions/extensionSettings.test.d.ts +6 -0
  60. package/dist/src/config/extensions/extensionSettings.test.js +254 -0
  61. package/dist/src/config/extensions/extensionSettings.test.js.map +1 -0
  62. package/dist/src/config/extensions/github.d.ts +2 -2
  63. package/dist/src/config/extensions/github.js +5 -10
  64. package/dist/src/config/extensions/github.js.map +1 -1
  65. package/dist/src/config/extensions/github.test.js +153 -167
  66. package/dist/src/config/extensions/github.test.js.map +1 -1
  67. package/dist/src/config/extensions/github_fetch.d.ts +1 -1
  68. package/dist/src/config/extensions/github_fetch.js +13 -1
  69. package/dist/src/config/extensions/github_fetch.js.map +1 -1
  70. package/dist/src/config/extensions/github_fetch.test.d.ts +6 -0
  71. package/dist/src/config/extensions/github_fetch.test.js +169 -0
  72. package/dist/src/config/extensions/github_fetch.test.js.map +1 -0
  73. package/dist/src/config/extensions/storage.d.ts +14 -0
  74. package/dist/src/config/extensions/storage.js +32 -0
  75. package/dist/src/config/extensions/storage.js.map +1 -0
  76. package/dist/src/config/extensions/update.d.ts +4 -4
  77. package/dist/src/config/extensions/update.js +39 -39
  78. package/dist/src/config/extensions/update.js.map +1 -1
  79. package/dist/src/config/extensions/update.test.js +72 -74
  80. package/dist/src/config/extensions/update.test.js.map +1 -1
  81. package/dist/src/config/extensions/variableSchema.d.ts +0 -6
  82. package/dist/src/config/extensions/variableSchema.js.map +1 -1
  83. package/dist/src/config/extensions/variables.d.ts +4 -0
  84. package/dist/src/config/extensions/variables.js +6 -0
  85. package/dist/src/config/extensions/variables.js.map +1 -1
  86. package/dist/src/config/keyBindings.d.ts +3 -0
  87. package/dist/src/config/keyBindings.js +30 -8
  88. package/dist/src/config/keyBindings.js.map +1 -1
  89. package/dist/src/config/keyBindings.test.js +17 -0
  90. package/dist/src/config/keyBindings.test.js.map +1 -1
  91. package/dist/src/config/policies/read-only.toml +56 -0
  92. package/dist/src/config/policies/write.toml +63 -0
  93. package/dist/src/config/policies/yolo.toml +31 -0
  94. package/dist/src/config/policy-engine.integration.test.js +41 -38
  95. package/dist/src/config/policy-engine.integration.test.js.map +1 -1
  96. package/dist/src/config/policy.d.ts +2 -2
  97. package/dist/src/config/policy.js +10 -148
  98. package/dist/src/config/policy.js.map +1 -1
  99. package/dist/src/config/sandboxConfig.d.ts +1 -1
  100. package/dist/src/config/sandboxConfig.js +6 -3
  101. package/dist/src/config/sandboxConfig.js.map +1 -1
  102. package/dist/src/config/settings.d.ts +2 -1
  103. package/dist/src/config/settings.js +58 -18
  104. package/dist/src/config/settings.js.map +1 -1
  105. package/dist/src/config/settings.test.js +128 -69
  106. package/dist/src/config/settings.test.js.map +1 -1
  107. package/dist/src/config/settingsSchema.d.ts +170 -28
  108. package/dist/src/config/settingsSchema.js +418 -27
  109. package/dist/src/config/settingsSchema.js.map +1 -1
  110. package/dist/src/config/settingsSchema.test.js +42 -1
  111. package/dist/src/config/settingsSchema.test.js.map +1 -1
  112. package/dist/src/config/trustedFolders.d.ts +1 -1
  113. package/dist/src/config/trustedFolders.js +4 -2
  114. package/dist/src/config/trustedFolders.js.map +1 -1
  115. package/dist/src/core/initializer.js +2 -1
  116. package/dist/src/core/initializer.js.map +1 -1
  117. package/dist/src/gemini.d.ts +1 -1
  118. package/dist/src/gemini.js +46 -16
  119. package/dist/src/gemini.js.map +1 -1
  120. package/dist/src/gemini.test.js +88 -30
  121. package/dist/src/gemini.test.js.map +1 -1
  122. package/dist/src/generated/git-commit.d.ts +2 -2
  123. package/dist/src/generated/git-commit.js +2 -2
  124. package/dist/src/nonInteractiveCli.d.ts +9 -1
  125. package/dist/src/nonInteractiveCli.js +114 -7
  126. package/dist/src/nonInteractiveCli.js.map +1 -1
  127. package/dist/src/nonInteractiveCli.test.js +355 -112
  128. package/dist/src/nonInteractiveCli.test.js.map +1 -1
  129. package/dist/src/services/BuiltinCommandLoader.js +4 -0
  130. package/dist/src/services/BuiltinCommandLoader.js.map +1 -1
  131. package/dist/src/services/BuiltinCommandLoader.test.js +22 -0
  132. package/dist/src/services/BuiltinCommandLoader.test.js.map +1 -1
  133. package/dist/src/services/FeedbackService.js +2 -2
  134. package/dist/src/services/FeedbackService.js.map +1 -1
  135. package/dist/src/services/McpPromptLoader.js +2 -2
  136. package/dist/src/services/McpPromptLoader.js.map +1 -1
  137. package/dist/src/services/McpPromptLoader.test.js +4 -2
  138. package/dist/src/services/McpPromptLoader.test.js.map +1 -1
  139. package/dist/src/test-utils/async.d.ts +9 -0
  140. package/dist/src/test-utils/async.js +29 -0
  141. package/dist/src/test-utils/async.js.map +1 -0
  142. package/dist/src/test-utils/createExtension.d.ts +3 -1
  143. package/dist/src/test-utils/createExtension.js +3 -3
  144. package/dist/src/test-utils/createExtension.js.map +1 -1
  145. package/dist/src/test-utils/render.d.ts +16 -2
  146. package/dist/src/test-utils/render.js +66 -4
  147. package/dist/src/test-utils/render.js.map +1 -1
  148. package/dist/src/test-utils/render.test.d.ts +6 -0
  149. package/dist/src/test-utils/render.test.js +79 -0
  150. package/dist/src/test-utils/render.test.js.map +1 -0
  151. package/dist/src/ui/App.test.js +1 -1
  152. package/dist/src/ui/App.test.js.map +1 -1
  153. package/dist/src/ui/AppContainer.js +181 -65
  154. package/dist/src/ui/AppContainer.js.map +1 -1
  155. package/dist/src/ui/AppContainer.test.js +505 -147
  156. package/dist/src/ui/AppContainer.test.js.map +1 -1
  157. package/dist/src/ui/IdeIntegrationNudge.js +1 -1
  158. package/dist/src/ui/IdeIntegrationNudge.js.map +1 -1
  159. package/dist/src/ui/auth/ApiAuthDialog.d.ts +14 -0
  160. package/dist/src/ui/auth/ApiAuthDialog.js +26 -0
  161. package/dist/src/ui/auth/ApiAuthDialog.js.map +1 -0
  162. package/dist/src/ui/auth/ApiAuthDialog.test.d.ts +6 -0
  163. package/dist/src/ui/auth/ApiAuthDialog.test.js +91 -0
  164. package/dist/src/ui/auth/ApiAuthDialog.test.js.map +1 -0
  165. package/dist/src/ui/auth/AuthDialog.js +7 -3
  166. package/dist/src/ui/auth/AuthDialog.js.map +1 -1
  167. package/dist/src/ui/auth/useAuth.d.ts +2 -0
  168. package/dist/src/ui/auth/useAuth.js +31 -2
  169. package/dist/src/ui/auth/useAuth.js.map +1 -1
  170. package/dist/src/ui/colors.js +3 -0
  171. package/dist/src/ui/colors.js.map +1 -1
  172. package/dist/src/ui/commands/directoryCommand.js +1 -1
  173. package/dist/src/ui/commands/directoryCommand.js.map +1 -1
  174. package/dist/src/ui/commands/extensionsCommand.js +64 -11
  175. package/dist/src/ui/commands/extensionsCommand.js.map +1 -1
  176. package/dist/src/ui/commands/extensionsCommand.test.js +72 -1
  177. package/dist/src/ui/commands/extensionsCommand.test.js.map +1 -1
  178. package/dist/src/ui/commands/mcpCommand.js +14 -14
  179. package/dist/src/ui/commands/mcpCommand.js.map +1 -1
  180. package/dist/src/ui/commands/mcpCommand.test.js +4 -0
  181. package/dist/src/ui/commands/mcpCommand.test.js.map +1 -1
  182. package/dist/src/ui/commands/memoryCommand.js +1 -1
  183. package/dist/src/ui/commands/memoryCommand.js.map +1 -1
  184. package/dist/src/ui/commands/memoryCommand.test.js +3 -1
  185. package/dist/src/ui/commands/memoryCommand.test.js.map +1 -1
  186. package/dist/src/ui/commands/policiesCommand.d.ts +7 -0
  187. package/dist/src/ui/commands/policiesCommand.js +59 -0
  188. package/dist/src/ui/commands/policiesCommand.js.map +1 -0
  189. package/dist/src/ui/commands/policiesCommand.test.d.ts +6 -0
  190. package/dist/src/ui/commands/policiesCommand.test.js +83 -0
  191. package/dist/src/ui/commands/policiesCommand.test.js.map +1 -0
  192. package/dist/src/ui/components/AnsiOutput.test.js +1 -1
  193. package/dist/src/ui/components/AnsiOutput.test.js.map +1 -1
  194. package/dist/src/ui/components/AsciiArt.d.ts +3 -3
  195. package/dist/src/ui/components/AsciiArt.js +3 -3
  196. package/dist/src/ui/components/Composer.js +1 -1
  197. package/dist/src/ui/components/Composer.js.map +1 -1
  198. package/dist/src/ui/components/Composer.test.js +5 -2
  199. package/dist/src/ui/components/Composer.test.js.map +1 -1
  200. package/dist/src/ui/components/ConfigInitDisplay.js +4 -6
  201. package/dist/src/ui/components/ConfigInitDisplay.js.map +1 -1
  202. package/dist/src/ui/components/ConsentPrompt.test.js +18 -8
  203. package/dist/src/ui/components/ConsentPrompt.test.js.map +1 -1
  204. package/dist/src/ui/components/ConsoleSummaryDisplay.js +1 -1
  205. package/dist/src/ui/components/ConsoleSummaryDisplay.js.map +1 -1
  206. package/dist/src/ui/components/ContextSummaryDisplay.test.js +11 -6
  207. package/dist/src/ui/components/ContextSummaryDisplay.test.js.map +1 -1
  208. package/dist/src/ui/components/DetailedMessagesDisplay.js +1 -1
  209. package/dist/src/ui/components/DetailedMessagesDisplay.js.map +1 -1
  210. package/dist/src/ui/components/DialogManager.js +4 -0
  211. package/dist/src/ui/components/DialogManager.js.map +1 -1
  212. package/dist/src/ui/components/FolderTrustDialog.test.js +2 -1
  213. package/dist/src/ui/components/FolderTrustDialog.test.js.map +1 -1
  214. package/dist/src/ui/components/Footer.js +4 -3
  215. package/dist/src/ui/components/Footer.js.map +1 -1
  216. package/dist/src/ui/components/Footer.test.js +83 -0
  217. package/dist/src/ui/components/Footer.test.js.map +1 -1
  218. package/dist/src/ui/components/Header.test.js +13 -5
  219. package/dist/src/ui/components/Header.test.js.map +1 -1
  220. package/dist/src/ui/components/Help.test.js +5 -4
  221. package/dist/src/ui/components/Help.test.js.map +1 -1
  222. package/dist/src/ui/components/HistoryItemDisplay.js +1 -1
  223. package/dist/src/ui/components/HistoryItemDisplay.js.map +1 -1
  224. package/dist/src/ui/components/InputPrompt.js +27 -8
  225. package/dist/src/ui/components/InputPrompt.js.map +1 -1
  226. package/dist/src/ui/components/InputPrompt.test.js +776 -727
  227. package/dist/src/ui/components/InputPrompt.test.js.map +1 -1
  228. package/dist/src/ui/components/LoadingIndicator.js +2 -2
  229. package/dist/src/ui/components/LoadingIndicator.js.map +1 -1
  230. package/dist/src/ui/components/LoadingIndicator.test.js +28 -15
  231. package/dist/src/ui/components/LoadingIndicator.test.js.map +1 -1
  232. package/dist/src/ui/components/LoopDetectionConfirmation.js +1 -1
  233. package/dist/src/ui/components/LoopDetectionConfirmation.js.map +1 -1
  234. package/dist/src/ui/components/LoopDetectionConfirmation.test.js +2 -2
  235. package/dist/src/ui/components/LoopDetectionConfirmation.test.js.map +1 -1
  236. package/dist/src/ui/components/MainContent.js +15 -4
  237. package/dist/src/ui/components/MainContent.js.map +1 -1
  238. package/dist/src/ui/components/ModelDialog.js +1 -1
  239. package/dist/src/ui/components/ModelDialog.js.map +1 -1
  240. package/dist/src/ui/components/ModelDialog.test.js +23 -13
  241. package/dist/src/ui/components/ModelDialog.test.js.map +1 -1
  242. package/dist/src/ui/components/ModelStatsDisplay.test.js +1 -1
  243. package/dist/src/ui/components/ModelStatsDisplay.test.js.map +1 -1
  244. package/dist/src/ui/components/Notifications.js +38 -5
  245. package/dist/src/ui/components/Notifications.js.map +1 -1
  246. package/dist/src/ui/components/PermissionsModifyTrustDialog.test.js +2 -2
  247. package/dist/src/ui/components/PermissionsModifyTrustDialog.test.js.map +1 -1
  248. package/dist/src/ui/components/PrepareLabel.test.js +14 -8
  249. package/dist/src/ui/components/PrepareLabel.test.js.map +1 -1
  250. package/dist/src/ui/components/ProQuotaDialog.test.js +14 -6
  251. package/dist/src/ui/components/ProQuotaDialog.test.js.map +1 -1
  252. package/dist/src/ui/components/QueuedMessageDisplay.test.js +11 -6
  253. package/dist/src/ui/components/QueuedMessageDisplay.test.js.map +1 -1
  254. package/dist/src/ui/components/SessionSummaryDisplay.test.js +1 -1
  255. package/dist/src/ui/components/SessionSummaryDisplay.test.js.map +1 -1
  256. package/dist/src/ui/components/SettingsDialog.js +32 -25
  257. package/dist/src/ui/components/SettingsDialog.js.map +1 -1
  258. package/dist/src/ui/components/SettingsDialog.test.js +428 -532
  259. package/dist/src/ui/components/SettingsDialog.test.js.map +1 -1
  260. package/dist/src/ui/components/ShellConfirmationDialog.js +1 -1
  261. package/dist/src/ui/components/ShellConfirmationDialog.js.map +1 -1
  262. package/dist/src/ui/components/ShellConfirmationDialog.test.js +2 -2
  263. package/dist/src/ui/components/ShellConfirmationDialog.test.js.map +1 -1
  264. package/dist/src/ui/components/StatsDisplay.test.js +1 -1
  265. package/dist/src/ui/components/StatsDisplay.test.js.map +1 -1
  266. package/dist/src/ui/components/SuggestionsDisplay.js +1 -1
  267. package/dist/src/ui/components/SuggestionsDisplay.js.map +1 -1
  268. package/dist/src/ui/components/ThemeDialog.test.js +2 -2
  269. package/dist/src/ui/components/ThemeDialog.test.js.map +1 -1
  270. package/dist/src/ui/components/ToolStatsDisplay.test.js +1 -1
  271. package/dist/src/ui/components/ToolStatsDisplay.test.js.map +1 -1
  272. package/dist/src/ui/components/messages/CompressionMessage.test.js +25 -17
  273. package/dist/src/ui/components/messages/CompressionMessage.test.js.map +1 -1
  274. package/dist/src/ui/components/messages/DiffRenderer.test.js +1 -1
  275. package/dist/src/ui/components/messages/DiffRenderer.test.js.map +1 -1
  276. package/dist/src/ui/components/messages/InfoMessage.js +1 -1
  277. package/dist/src/ui/components/messages/InfoMessage.js.map +1 -1
  278. package/dist/src/ui/components/messages/Todo.js +27 -5
  279. package/dist/src/ui/components/messages/Todo.js.map +1 -1
  280. package/dist/src/ui/components/messages/Todo.test.js +20 -8
  281. package/dist/src/ui/components/messages/Todo.test.js.map +1 -1
  282. package/dist/src/ui/components/messages/ToolConfirmationMessage.js +1 -1
  283. package/dist/src/ui/components/messages/ToolConfirmationMessage.js.map +1 -1
  284. package/dist/src/ui/components/messages/ToolGroupMessage.test.js +29 -15
  285. package/dist/src/ui/components/messages/ToolGroupMessage.test.js.map +1 -1
  286. package/dist/src/ui/components/messages/WarningMessage.js +2 -2
  287. package/dist/src/ui/components/messages/WarningMessage.js.map +1 -1
  288. package/dist/src/ui/components/shared/BaseSelectionList.test.js +1 -1
  289. package/dist/src/ui/components/shared/BaseSelectionList.test.js.map +1 -1
  290. package/dist/src/ui/components/shared/MaxSizedBox.test.js +43 -22
  291. package/dist/src/ui/components/shared/MaxSizedBox.test.js.map +1 -1
  292. package/dist/src/ui/components/shared/TextInput.d.ts +15 -0
  293. package/dist/src/ui/components/shared/TextInput.js +38 -0
  294. package/dist/src/ui/components/shared/TextInput.js.map +1 -0
  295. package/dist/src/ui/components/shared/TextInput.test.d.ts +6 -0
  296. package/dist/src/ui/components/shared/TextInput.test.js +242 -0
  297. package/dist/src/ui/components/shared/TextInput.test.js.map +1 -0
  298. package/dist/src/ui/components/shared/text-buffer.d.ts +9 -2
  299. package/dist/src/ui/components/shared/text-buffer.js +51 -13
  300. package/dist/src/ui/components/shared/text-buffer.js.map +1 -1
  301. package/dist/src/ui/components/shared/text-buffer.test.js +385 -202
  302. package/dist/src/ui/components/shared/text-buffer.test.js.map +1 -1
  303. package/dist/src/ui/components/views/ChatList.test.js +7 -4
  304. package/dist/src/ui/components/views/ChatList.test.js.map +1 -1
  305. package/dist/src/ui/components/views/ExtensionsList.d.ts +7 -1
  306. package/dist/src/ui/components/views/ExtensionsList.js +9 -11
  307. package/dist/src/ui/components/views/ExtensionsList.js.map +1 -1
  308. package/dist/src/ui/components/views/ExtensionsList.test.js +43 -22
  309. package/dist/src/ui/components/views/ExtensionsList.test.js.map +1 -1
  310. package/dist/src/ui/components/views/McpStatus.test.js +23 -12
  311. package/dist/src/ui/components/views/McpStatus.test.js.map +1 -1
  312. package/dist/src/ui/contexts/KeypressContext.d.ts +3 -2
  313. package/dist/src/ui/contexts/KeypressContext.js +610 -540
  314. package/dist/src/ui/contexts/KeypressContext.js.map +1 -1
  315. package/dist/src/ui/contexts/KeypressContext.test.js +438 -718
  316. package/dist/src/ui/contexts/KeypressContext.test.js.map +1 -1
  317. package/dist/src/ui/contexts/MouseContext.d.ts +21 -0
  318. package/dist/src/ui/contexts/MouseContext.js +89 -0
  319. package/dist/src/ui/contexts/MouseContext.js.map +1 -0
  320. package/dist/src/ui/contexts/MouseContext.test.d.ts +6 -0
  321. package/dist/src/ui/contexts/MouseContext.test.js +164 -0
  322. package/dist/src/ui/contexts/MouseContext.test.js.map +1 -0
  323. package/dist/src/ui/contexts/SessionContext.test.js +35 -17
  324. package/dist/src/ui/contexts/SessionContext.test.js.map +1 -1
  325. package/dist/src/ui/contexts/UIActionsContext.d.ts +2 -0
  326. package/dist/src/ui/contexts/UIActionsContext.js.map +1 -1
  327. package/dist/src/ui/contexts/UIStateContext.d.ts +2 -0
  328. package/dist/src/ui/contexts/UIStateContext.js.map +1 -1
  329. package/dist/src/ui/hooks/atCommandProcessor.js +31 -9
  330. package/dist/src/ui/hooks/atCommandProcessor.js.map +1 -1
  331. package/dist/src/ui/hooks/atCommandProcessor.test.js +163 -64
  332. package/dist/src/ui/hooks/atCommandProcessor.test.js.map +1 -1
  333. package/dist/src/ui/hooks/shellCommandProcessor.test.js +64 -35
  334. package/dist/src/ui/hooks/shellCommandProcessor.test.js.map +1 -1
  335. package/dist/src/ui/hooks/slashCommandProcessor.test.js +193 -165
  336. package/dist/src/ui/hooks/slashCommandProcessor.test.js.map +1 -1
  337. package/dist/src/ui/hooks/useAtCompletion.test.js +16 -5
  338. package/dist/src/ui/hooks/useAtCompletion.test.js.map +1 -1
  339. package/dist/src/ui/hooks/useAutoAcceptIndicator.js +10 -0
  340. package/dist/src/ui/hooks/useAutoAcceptIndicator.js.map +1 -1
  341. package/dist/src/ui/hooks/useAutoAcceptIndicator.test.js +32 -1
  342. package/dist/src/ui/hooks/useAutoAcceptIndicator.test.js.map +1 -1
  343. package/dist/src/ui/hooks/useCommandCompletion.test.js +66 -64
  344. package/dist/src/ui/hooks/useCommandCompletion.test.js.map +1 -1
  345. package/dist/src/ui/hooks/useConsoleMessages.test.js +26 -9
  346. package/dist/src/ui/hooks/useConsoleMessages.test.js.map +1 -1
  347. package/dist/src/ui/hooks/useEditorSettings.test.js +40 -34
  348. package/dist/src/ui/hooks/useEditorSettings.test.js.map +1 -1
  349. package/dist/src/ui/hooks/useExtensionUpdates.d.ts +14 -5
  350. package/dist/src/ui/hooks/useExtensionUpdates.js +18 -13
  351. package/dist/src/ui/hooks/useExtensionUpdates.js.map +1 -1
  352. package/dist/src/ui/hooks/useExtensionUpdates.test.js +49 -44
  353. package/dist/src/ui/hooks/useExtensionUpdates.test.js.map +1 -1
  354. package/dist/src/ui/hooks/useFlickerDetector.test.js +9 -5
  355. package/dist/src/ui/hooks/useFlickerDetector.test.js.map +1 -1
  356. package/dist/src/ui/hooks/useFocus.test.js +25 -9
  357. package/dist/src/ui/hooks/useFocus.test.js.map +1 -1
  358. package/dist/src/ui/hooks/useFolderTrust.test.js +46 -22
  359. package/dist/src/ui/hooks/useFolderTrust.test.js.map +1 -1
  360. package/dist/src/ui/hooks/useGeminiStream.js +56 -19
  361. package/dist/src/ui/hooks/useGeminiStream.js.map +1 -1
  362. package/dist/src/ui/hooks/useGeminiStream.test.js +260 -411
  363. package/dist/src/ui/hooks/useGeminiStream.test.js.map +1 -1
  364. package/dist/src/ui/hooks/useGitBranchName.js +4 -0
  365. package/dist/src/ui/hooks/useGitBranchName.js.map +1 -1
  366. package/dist/src/ui/hooks/useGitBranchName.test.js +46 -34
  367. package/dist/src/ui/hooks/useGitBranchName.test.js.map +1 -1
  368. package/dist/src/ui/hooks/useHistoryManager.test.js +2 -1
  369. package/dist/src/ui/hooks/useHistoryManager.test.js.map +1 -1
  370. package/dist/src/ui/hooks/useIdeTrustListener.test.js +40 -9
  371. package/dist/src/ui/hooks/useIdeTrustListener.test.js.map +1 -1
  372. package/dist/src/ui/hooks/useInputHistory.test.js +2 -1
  373. package/dist/src/ui/hooks/useInputHistory.test.js.map +1 -1
  374. package/dist/src/ui/hooks/useInputHistoryStore.test.js +2 -1
  375. package/dist/src/ui/hooks/useInputHistoryStore.test.js.map +1 -1
  376. package/dist/src/ui/hooks/useKeypress.test.js +103 -114
  377. package/dist/src/ui/hooks/useKeypress.test.js.map +1 -1
  378. package/dist/src/ui/hooks/useLoadingIndicator.test.js +24 -6
  379. package/dist/src/ui/hooks/useLoadingIndicator.test.js.map +1 -1
  380. package/dist/src/ui/hooks/useMemoryMonitor.test.js +10 -5
  381. package/dist/src/ui/hooks/useMemoryMonitor.test.js.map +1 -1
  382. package/dist/src/ui/hooks/useMessageQueue.test.js +62 -45
  383. package/dist/src/ui/hooks/useMessageQueue.test.js.map +1 -1
  384. package/dist/src/ui/hooks/useModelCommand.test.js +21 -11
  385. package/dist/src/ui/hooks/useModelCommand.test.js.map +1 -1
  386. package/dist/src/ui/hooks/useMouse.d.ts +17 -0
  387. package/dist/src/ui/hooks/useMouse.js +27 -0
  388. package/dist/src/ui/hooks/useMouse.js.map +1 -0
  389. package/dist/src/ui/hooks/useMouse.test.d.ts +6 -0
  390. package/dist/src/ui/hooks/useMouse.test.js +57 -0
  391. package/dist/src/ui/hooks/useMouse.test.js.map +1 -0
  392. package/dist/src/ui/hooks/usePermissionsModifyTrust.test.js +2 -2
  393. package/dist/src/ui/hooks/usePermissionsModifyTrust.test.js.map +1 -1
  394. package/dist/src/ui/hooks/usePhraseCycler.js +1 -1
  395. package/dist/src/ui/hooks/usePhraseCycler.js.map +1 -1
  396. package/dist/src/ui/hooks/usePhraseCycler.test.js +109 -106
  397. package/dist/src/ui/hooks/usePhraseCycler.test.js.map +1 -1
  398. package/dist/src/ui/hooks/usePrivacySettings.test.js +26 -6
  399. package/dist/src/ui/hooks/usePrivacySettings.test.js.map +1 -1
  400. package/dist/src/ui/hooks/usePromptCompletion.js +2 -2
  401. package/dist/src/ui/hooks/usePromptCompletion.js.map +1 -1
  402. package/dist/src/ui/hooks/useQuotaAndFallback.js +13 -14
  403. package/dist/src/ui/hooks/useQuotaAndFallback.js.map +1 -1
  404. package/dist/src/ui/hooks/useQuotaAndFallback.test.js +55 -48
  405. package/dist/src/ui/hooks/useQuotaAndFallback.test.js.map +1 -1
  406. package/dist/src/ui/hooks/useReactToolScheduler.d.ts +8 -1
  407. package/dist/src/ui/hooks/useReactToolScheduler.js +59 -34
  408. package/dist/src/ui/hooks/useReactToolScheduler.js.map +1 -1
  409. package/dist/src/ui/hooks/useReactToolScheduler.test.d.ts +6 -0
  410. package/dist/src/ui/hooks/useReactToolScheduler.test.js +65 -0
  411. package/dist/src/ui/hooks/useReactToolScheduler.test.js.map +1 -0
  412. package/dist/src/ui/hooks/useReverseSearchCompletion.test.js +2 -2
  413. package/dist/src/ui/hooks/useReverseSearchCompletion.test.js.map +1 -1
  414. package/dist/src/ui/hooks/useSelectionList.js +5 -4
  415. package/dist/src/ui/hooks/useSelectionList.js.map +1 -1
  416. package/dist/src/ui/hooks/useSelectionList.test.js +272 -183
  417. package/dist/src/ui/hooks/useSelectionList.test.js.map +1 -1
  418. package/dist/src/ui/hooks/useShellHistory.test.js +52 -20
  419. package/dist/src/ui/hooks/useShellHistory.test.js.map +1 -1
  420. package/dist/src/ui/hooks/useSlashCompletion.js +18 -7
  421. package/dist/src/ui/hooks/useSlashCompletion.js.map +1 -1
  422. package/dist/src/ui/hooks/useSlashCompletion.test.js +275 -137
  423. package/dist/src/ui/hooks/useSlashCompletion.test.js.map +1 -1
  424. package/dist/src/ui/hooks/useTimer.test.js +43 -14
  425. package/dist/src/ui/hooks/useTimer.test.js.map +1 -1
  426. package/dist/src/ui/hooks/useToolScheduler.test.js +226 -242
  427. package/dist/src/ui/hooks/useToolScheduler.test.js.map +1 -1
  428. package/dist/src/ui/hooks/vim.test.js +235 -355
  429. package/dist/src/ui/hooks/vim.test.js.map +1 -1
  430. package/dist/src/ui/keyMatchers.test.js +30 -3
  431. package/dist/src/ui/keyMatchers.test.js.map +1 -1
  432. package/dist/src/ui/state/extensions.d.ts +1 -0
  433. package/dist/src/ui/state/extensions.js +1 -0
  434. package/dist/src/ui/state/extensions.js.map +1 -1
  435. package/dist/src/ui/themes/ansi-light.js +1 -0
  436. package/dist/src/ui/themes/ansi-light.js.map +1 -1
  437. package/dist/src/ui/themes/ansi.js +1 -0
  438. package/dist/src/ui/themes/ansi.js.map +1 -1
  439. package/dist/src/ui/themes/atom-one-dark.js +2 -0
  440. package/dist/src/ui/themes/atom-one-dark.js.map +1 -1
  441. package/dist/src/ui/themes/ayu-light.js +2 -0
  442. package/dist/src/ui/themes/ayu-light.js.map +1 -1
  443. package/dist/src/ui/themes/ayu.js +2 -0
  444. package/dist/src/ui/themes/ayu.js.map +1 -1
  445. package/dist/src/ui/themes/color-utils.d.ts +1 -0
  446. package/dist/src/ui/themes/color-utils.js +6 -0
  447. package/dist/src/ui/themes/color-utils.js.map +1 -1
  448. package/dist/src/ui/themes/color-utils.test.js +13 -1
  449. package/dist/src/ui/themes/color-utils.test.js.map +1 -1
  450. package/dist/src/ui/themes/dracula.js +2 -0
  451. package/dist/src/ui/themes/dracula.js.map +1 -1
  452. package/dist/src/ui/themes/github-dark.js +2 -0
  453. package/dist/src/ui/themes/github-dark.js.map +1 -1
  454. package/dist/src/ui/themes/github-light.js +2 -0
  455. package/dist/src/ui/themes/github-light.js.map +1 -1
  456. package/dist/src/ui/themes/googlecode.js +2 -0
  457. package/dist/src/ui/themes/googlecode.js.map +1 -1
  458. package/dist/src/ui/themes/no-color.js +3 -0
  459. package/dist/src/ui/themes/no-color.js.map +1 -1
  460. package/dist/src/ui/themes/semantic-tokens.d.ts +2 -0
  461. package/dist/src/ui/themes/semantic-tokens.js +6 -0
  462. package/dist/src/ui/themes/semantic-tokens.js.map +1 -1
  463. package/dist/src/ui/themes/shades-of-purple.js +2 -0
  464. package/dist/src/ui/themes/shades-of-purple.js.map +1 -1
  465. package/dist/src/ui/themes/theme.d.ts +3 -0
  466. package/dist/src/ui/themes/theme.js +14 -3
  467. package/dist/src/ui/themes/theme.js.map +1 -1
  468. package/dist/src/ui/themes/theme.test.js +67 -1
  469. package/dist/src/ui/themes/theme.test.js.map +1 -1
  470. package/dist/src/ui/themes/xcode.js +2 -0
  471. package/dist/src/ui/themes/xcode.js.map +1 -1
  472. package/dist/src/ui/types.d.ts +3 -1
  473. package/dist/src/ui/types.js +2 -0
  474. package/dist/src/ui/types.js.map +1 -1
  475. package/dist/src/ui/utils/CodeColorizer.js +2 -1
  476. package/dist/src/ui/utils/CodeColorizer.js.map +1 -1
  477. package/dist/src/ui/utils/InlineMarkdownRenderer.d.ts +1 -0
  478. package/dist/src/ui/utils/InlineMarkdownRenderer.js +11 -10
  479. package/dist/src/ui/utils/InlineMarkdownRenderer.js.map +1 -1
  480. package/dist/src/ui/utils/MarkdownDisplay.js +11 -9
  481. package/dist/src/ui/utils/MarkdownDisplay.js.map +1 -1
  482. package/dist/src/ui/utils/clipboardUtils.js +2 -2
  483. package/dist/src/ui/utils/clipboardUtils.js.map +1 -1
  484. package/dist/src/ui/utils/input.d.ts +17 -0
  485. package/dist/src/ui/utils/input.js +51 -0
  486. package/dist/src/ui/utils/input.js.map +1 -0
  487. package/dist/src/ui/utils/input.test.d.ts +6 -0
  488. package/dist/src/ui/utils/input.test.js +44 -0
  489. package/dist/src/ui/utils/input.test.js.map +1 -0
  490. package/dist/src/ui/utils/kittyProtocolDetector.js +13 -4
  491. package/dist/src/ui/utils/kittyProtocolDetector.js.map +1 -1
  492. package/dist/src/ui/utils/mouse.d.ts +31 -0
  493. package/dist/src/ui/utils/mouse.js +164 -0
  494. package/dist/src/ui/utils/mouse.js.map +1 -0
  495. package/dist/src/ui/utils/mouse.test.d.ts +6 -0
  496. package/dist/src/ui/utils/mouse.test.js +131 -0
  497. package/dist/src/ui/utils/mouse.test.js.map +1 -0
  498. package/dist/src/ui/utils/textOutput.d.ts +25 -0
  499. package/dist/src/ui/utils/textOutput.js +49 -0
  500. package/dist/src/ui/utils/textOutput.js.map +1 -0
  501. package/dist/src/ui/utils/textOutput.test.d.ts +6 -0
  502. package/dist/src/ui/utils/textOutput.test.js +79 -0
  503. package/dist/src/ui/utils/textOutput.test.js.map +1 -0
  504. package/dist/src/ui/utils/updateCheck.d.ts +7 -1
  505. package/dist/src/ui/utils/updateCheck.js +33 -29
  506. package/dist/src/ui/utils/updateCheck.js.map +1 -1
  507. package/dist/src/ui/utils/updateCheck.test.js +24 -50
  508. package/dist/src/ui/utils/updateCheck.test.js.map +1 -1
  509. package/dist/src/utils/commentJson.js +2 -2
  510. package/dist/src/utils/commentJson.js.map +1 -1
  511. package/dist/src/utils/commentJson.test.js +7 -6
  512. package/dist/src/utils/commentJson.test.js.map +1 -1
  513. package/dist/src/utils/envVarResolver.d.ts +2 -2
  514. package/dist/src/utils/envVarResolver.js +10 -7
  515. package/dist/src/utils/envVarResolver.js.map +1 -1
  516. package/dist/src/utils/events.d.ts +11 -2
  517. package/dist/src/utils/events.js +1 -0
  518. package/dist/src/utils/events.js.map +1 -1
  519. package/dist/src/utils/handleAutoUpdate.js +9 -3
  520. package/dist/src/utils/handleAutoUpdate.js.map +1 -1
  521. package/dist/src/utils/sandbox.js +16 -18
  522. package/dist/src/utils/sandbox.js.map +1 -1
  523. package/dist/src/utils/version.js +6 -2
  524. package/dist/src/utils/version.js.map +1 -1
  525. package/dist/src/zed-integration/acp.js +2 -1
  526. package/dist/src/zed-integration/acp.js.map +1 -1
  527. package/dist/src/zed-integration/schema.d.ts +4 -4
  528. package/dist/src/zed-integration/zedIntegration.d.ts +2 -2
  529. package/dist/src/zed-integration/zedIntegration.js +12 -19
  530. package/dist/src/zed-integration/zedIntegration.js.map +1 -1
  531. package/dist/tsconfig.tsbuildinfo +1 -1
  532. package/package.json +18 -14
  533. package/dist/src/config/policy.test.js +0 -360
  534. package/dist/src/config/policy.test.js.map +0 -1
  535. package/dist/src/utils/package.d.ts +0 -12
  536. package/dist/src/utils/package.js +0 -24
  537. package/dist/src/utils/package.js.map +0 -1
  538. /package/dist/src/{config/policy.test.d.ts → commands/extensions/validate.test.d.ts} +0 -0
@@ -1,5 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { renderHook, act, waitFor } from '@testing-library/react';
2
+ import { act } from 'react';
3
+ import { renderHook } from '../../test-utils/render.js';
4
+ import { waitFor } from '../../test-utils/async.js';
3
5
  import { vi } from 'vitest';
4
6
  import { KeypressProvider, useKeypressContext, DRAG_COMPLETION_TIMEOUT_MS, KITTY_SEQUENCE_TIMEOUT_MS,
5
7
  // CSI_END_O,
@@ -15,31 +17,30 @@ vi.mock('ink', async (importOriginal) => {
15
17
  useStdin: vi.fn(),
16
18
  };
17
19
  });
20
+ const PASTE_START = '\x1B[200~';
21
+ const PASTE_END = '\x1B[201~';
22
+ // readline will not emit most incomplete kitty sequences but it will give
23
+ // up on sequences like this where the modifier (135) has more than two digits.
24
+ const INCOMPLETE_KITTY_SEQUENCE = '\x1b[97;135';
18
25
  class MockStdin extends EventEmitter {
19
26
  isTTY = true;
20
27
  setRawMode = vi.fn();
21
28
  on = this.addListener;
22
29
  removeListener = super.removeListener;
23
- write = vi.fn();
24
30
  resume = vi.fn();
25
31
  pause = vi.fn();
26
- // Helper to simulate a keypress event
27
- pressKey(key) {
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));
32
+ write(text) {
33
+ this.emit('data', text);
41
34
  }
42
35
  }
36
+ // Helper function to setup keypress test with standard configuration
37
+ const setupKeypressTest = (kittyProtocolEnabled = true) => {
38
+ const keyHandler = vi.fn();
39
+ const wrapper = ({ children }) => (_jsx(KeypressProvider, { kittyProtocolEnabled: kittyProtocolEnabled, children: children }));
40
+ const { result } = renderHook(() => useKeypressContext(), { wrapper });
41
+ act(() => result.current.subscribe(keyHandler));
42
+ return { result, keyHandler };
43
+ };
43
44
  describe('KeypressContext - Kitty Protocol', () => {
44
45
  let stdin;
45
46
  const mockSetRawMode = vi.fn();
@@ -53,117 +54,58 @@ describe('KeypressContext - Kitty Protocol', () => {
53
54
  });
54
55
  });
55
56
  describe('Enter key handling', () => {
56
- it('should recognize regular enter key (keycode 13) in kitty protocol', async () => {
57
- const keyHandler = vi.fn();
58
- const { result } = renderHook(() => useKeypressContext(), {
59
- wrapper: ({ children }) => wrapper({ children, kittyProtocolEnabled: true }),
60
- });
61
- act(() => {
62
- result.current.subscribe(keyHandler);
63
- });
64
- // Send kitty protocol sequence for regular enter: ESC[13u
65
- act(() => {
66
- stdin.sendKittySequence(`\x1b[13u`);
67
- });
68
- expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
69
- name: 'return',
70
- kittyProtocol: true,
71
- ctrl: false,
72
- meta: false,
73
- shift: false,
74
- }));
75
- });
76
- it('should recognize numpad enter key (keycode 57414) in kitty protocol', async () => {
77
- const keyHandler = vi.fn();
78
- const { result } = renderHook(() => useKeypressContext(), {
79
- wrapper: ({ children }) => wrapper({ children, kittyProtocolEnabled: true }),
80
- });
81
- act(() => {
82
- result.current.subscribe(keyHandler);
83
- });
84
- // Send kitty protocol sequence for numpad enter: ESC[57414u
85
- act(() => {
86
- stdin.sendKittySequence(`\x1b[57414u`);
87
- });
88
- expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
89
- name: 'return',
90
- kittyProtocol: true,
91
- ctrl: false,
92
- meta: false,
93
- shift: false,
94
- }));
95
- });
96
- it('should handle numpad enter with modifiers', async () => {
97
- const keyHandler = vi.fn();
98
- const { result } = renderHook(() => useKeypressContext(), {
99
- wrapper: ({ children }) => wrapper({ children, kittyProtocolEnabled: true }),
100
- });
101
- act(() => {
102
- result.current.subscribe(keyHandler);
103
- });
104
- // Send kitty protocol sequence for numpad enter with Shift (modifier 2): ESC[57414;2u
57
+ it.each([
58
+ {
59
+ name: 'regular enter key (keycode 13)',
60
+ sequence: '\x1b[13u',
61
+ },
62
+ {
63
+ name: 'numpad enter key (keycode 57414)',
64
+ sequence: '\x1b[57414u',
65
+ },
66
+ ])('should recognize $name in kitty protocol', async ({ sequence }) => {
67
+ const { keyHandler } = setupKeypressTest(true);
105
68
  act(() => {
106
- stdin.sendKittySequence(`\x1b[57414;2u`);
69
+ stdin.write(sequence);
107
70
  });
108
71
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
109
72
  name: 'return',
110
73
  kittyProtocol: true,
111
74
  ctrl: false,
112
75
  meta: false,
113
- shift: true,
114
- }));
115
- });
116
- it('should handle numpad enter with Ctrl modifier', async () => {
117
- const keyHandler = vi.fn();
118
- const { result } = renderHook(() => useKeypressContext(), {
119
- wrapper: ({ children }) => wrapper({ children, kittyProtocolEnabled: true }),
120
- });
121
- act(() => {
122
- result.current.subscribe(keyHandler);
123
- });
124
- // Send kitty protocol sequence for numpad enter with Ctrl (modifier 5): ESC[57414;5u
125
- act(() => {
126
- stdin.sendKittySequence(`\x1b[57414;5u`);
127
- });
128
- expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
129
- name: 'return',
130
- kittyProtocol: true,
131
- ctrl: true,
132
- meta: false,
133
76
  shift: false,
134
77
  }));
135
78
  });
136
- it('should handle numpad enter with Alt modifier', async () => {
137
- const keyHandler = vi.fn();
138
- const { result } = renderHook(() => useKeypressContext(), {
139
- wrapper: ({ children }) => wrapper({ children, kittyProtocolEnabled: true }),
140
- });
141
- act(() => {
142
- result.current.subscribe(keyHandler);
143
- });
144
- // Send kitty protocol sequence for numpad enter with Alt (modifier 3): ESC[57414;3u
145
- act(() => {
146
- stdin.sendKittySequence(`\x1b[57414;3u`);
147
- });
79
+ it.each([
80
+ {
81
+ modifier: 'Shift',
82
+ sequence: '\x1b[57414;2u',
83
+ expected: { ctrl: false, meta: false, shift: true },
84
+ },
85
+ {
86
+ modifier: 'Ctrl',
87
+ sequence: '\x1b[57414;5u',
88
+ expected: { ctrl: true, meta: false, shift: false },
89
+ },
90
+ {
91
+ modifier: 'Alt',
92
+ sequence: '\x1b[57414;3u',
93
+ expected: { ctrl: false, meta: true, shift: false },
94
+ },
95
+ ])('should handle numpad enter with $modifier modifier', async ({ sequence, expected }) => {
96
+ const { keyHandler } = setupKeypressTest(true);
97
+ act(() => stdin.write(sequence));
148
98
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
149
99
  name: 'return',
150
100
  kittyProtocol: true,
151
- ctrl: false,
152
- meta: true,
153
- shift: false,
101
+ ...expected,
154
102
  }));
155
103
  });
156
104
  it('should not process kitty sequences when kitty protocol is disabled', async () => {
157
- const keyHandler = vi.fn();
158
- const { result } = renderHook(() => useKeypressContext(), {
159
- wrapper: ({ children }) => wrapper({ children, kittyProtocolEnabled: false }),
160
- });
161
- act(() => {
162
- result.current.subscribe(keyHandler);
163
- });
105
+ const { keyHandler } = setupKeypressTest(false);
164
106
  // Send kitty protocol sequence for numpad enter
165
107
  act(() => {
166
- stdin.sendKittySequence(`\x1b[57414u`);
108
+ stdin.write(`\x1b[57414u`);
167
109
  });
168
110
  // When kitty protocol is disabled, the sequence should be passed through
169
111
  // as individual keypresses, not recognized as a single enter key
@@ -175,112 +117,115 @@ describe('KeypressContext - Kitty Protocol', () => {
175
117
  });
176
118
  describe('Escape key handling', () => {
177
119
  it('should recognize escape key (keycode 27) in kitty protocol', async () => {
178
- const keyHandler = vi.fn();
179
- const { result } = renderHook(() => useKeypressContext(), {
180
- wrapper: ({ children }) => wrapper({ children, kittyProtocolEnabled: true }),
181
- });
182
- act(() => {
183
- result.current.subscribe(keyHandler);
184
- });
120
+ const { keyHandler } = setupKeypressTest(true);
185
121
  // Send kitty protocol sequence for escape: ESC[27u
186
122
  act(() => {
187
- stdin.sendKittySequence('\x1b[27u');
123
+ stdin.write('\x1b[27u');
188
124
  });
189
125
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
190
126
  name: 'escape',
191
127
  kittyProtocol: true,
192
128
  }));
193
129
  });
194
- });
195
- describe('Tab and Backspace handling', () => {
196
- it('should recognize Tab key in kitty protocol', async () => {
197
- const keyHandler = vi.fn();
198
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
199
- act(() => result.current.subscribe(keyHandler));
200
- act(() => {
201
- stdin.sendKittySequence(`\x1b[9u`);
202
- });
203
- expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
204
- name: 'tab',
205
- kittyProtocol: true,
206
- shift: false,
207
- }));
208
- });
209
- it('should recognize Shift+Tab in kitty protocol', async () => {
210
- const keyHandler = vi.fn();
211
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
212
- act(() => result.current.subscribe(keyHandler));
213
- // Modifier 2 is Shift
214
- act(() => {
215
- stdin.sendKittySequence(`\x1b[9;2u`);
216
- });
217
- expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
218
- name: 'tab',
219
- kittyProtocol: true,
220
- shift: true,
221
- }));
222
- });
223
- it('should recognize Backspace key in kitty protocol', async () => {
224
- const keyHandler = vi.fn();
225
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
226
- act(() => result.current.subscribe(keyHandler));
227
- act(() => {
228
- stdin.sendKittySequence(`\x1b[127u`);
229
- });
230
- expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
231
- name: 'backspace',
232
- kittyProtocol: true,
233
- meta: false,
234
- }));
235
- });
236
- it('should recognize Option+Backspace in kitty protocol', async () => {
130
+ it('should handle lone Escape key (keycode 27) with timeout when kitty protocol is enabled', async () => {
131
+ // Use real timers for this test to avoid issues with stream/buffer timing
132
+ vi.useRealTimers();
237
133
  const keyHandler = vi.fn();
134
+ const wrapper = ({ children }) => (_jsx(KeypressProvider, { kittyProtocolEnabled: true, children: children }));
238
135
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
239
136
  act(() => result.current.subscribe(keyHandler));
240
- // Modifier 3 is Alt/Option
137
+ // Send just ESC
241
138
  act(() => {
242
- stdin.sendKittySequence(`\x1b[127;3u`);
139
+ stdin.write('\x1b');
243
140
  });
244
- expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
245
- name: 'backspace',
246
- kittyProtocol: true,
247
- meta: true,
248
- }));
141
+ // Should be buffered initially
142
+ expect(keyHandler).not.toHaveBeenCalled();
143
+ // Wait for timeout
144
+ await waitFor(() => {
145
+ expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
146
+ name: 'escape',
147
+ meta: true,
148
+ }));
149
+ }, { timeout: 500 });
249
150
  });
250
- it('should recognize Ctrl+Backspace in kitty protocol', async () => {
251
- const keyHandler = vi.fn();
252
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
253
- act(() => result.current.subscribe(keyHandler));
254
- // Modifier 5 is Ctrl
151
+ });
152
+ describe('Tab and Backspace handling', () => {
153
+ it.each([
154
+ {
155
+ name: 'Tab key',
156
+ sequence: '\x1b[9u',
157
+ expected: { name: 'tab', shift: false },
158
+ },
159
+ {
160
+ name: 'Shift+Tab',
161
+ sequence: '\x1b[9;2u',
162
+ expected: { name: 'tab', shift: true },
163
+ },
164
+ {
165
+ name: 'Backspace',
166
+ sequence: '\x1b[127u',
167
+ expected: { name: 'backspace', meta: false },
168
+ },
169
+ {
170
+ name: 'Option+Backspace',
171
+ sequence: '\x1b[127;3u',
172
+ expected: { name: 'backspace', meta: true },
173
+ },
174
+ {
175
+ name: 'Ctrl+Backspace',
176
+ sequence: '\x1b[127;5u',
177
+ expected: { name: 'backspace', ctrl: true },
178
+ },
179
+ ])('should recognize $name in kitty protocol', async ({ sequence, expected }) => {
180
+ const { keyHandler } = setupKeypressTest(true);
255
181
  act(() => {
256
- stdin.sendKittySequence(`\x1b[127;5u`);
182
+ stdin.write(sequence);
257
183
  });
258
184
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
259
- name: 'backspace',
185
+ ...expected,
260
186
  kittyProtocol: true,
261
- ctrl: true,
262
187
  }));
263
188
  });
264
189
  });
265
190
  describe('paste mode', () => {
266
- it('should handle multiline paste as a single event', async () => {
191
+ it.each([
192
+ {
193
+ name: 'handle multiline paste as a single event',
194
+ pastedText: 'This \n is \n a \n multiline \n paste.',
195
+ writeSequence: (text) => {
196
+ stdin.write(PASTE_START);
197
+ stdin.write(text);
198
+ stdin.write(PASTE_END);
199
+ },
200
+ },
201
+ {
202
+ name: 'handle paste start code split over multiple writes',
203
+ pastedText: 'pasted content',
204
+ writeSequence: (text) => {
205
+ stdin.write(PASTE_START.slice(0, 3));
206
+ stdin.write(PASTE_START.slice(3));
207
+ stdin.write(text);
208
+ stdin.write(PASTE_END);
209
+ },
210
+ },
211
+ {
212
+ name: 'handle paste end code split over multiple writes',
213
+ pastedText: 'pasted content',
214
+ writeSequence: (text) => {
215
+ stdin.write(PASTE_START);
216
+ stdin.write(text);
217
+ stdin.write(PASTE_END.slice(0, 3));
218
+ stdin.write(PASTE_END.slice(3));
219
+ },
220
+ },
221
+ ])('should $name', async ({ pastedText, writeSequence }) => {
267
222
  const keyHandler = vi.fn();
268
- const pastedText = 'This \n is \n a \n multiline \n paste.';
269
- const { result } = renderHook(() => useKeypressContext(), {
270
- wrapper,
271
- });
272
- act(() => {
273
- result.current.subscribe(keyHandler);
274
- });
275
- // Simulate a bracketed paste event
276
- act(() => {
277
- stdin.sendPaste(pastedText);
278
- });
223
+ const { result } = renderHook(() => useKeypressContext(), { wrapper });
224
+ act(() => result.current.subscribe(keyHandler));
225
+ act(() => writeSequence(pastedText));
279
226
  await waitFor(() => {
280
- // Expect the handler to be called exactly once for the entire paste
281
227
  expect(keyHandler).toHaveBeenCalledTimes(1);
282
228
  });
283
- // Verify the single event contains the full pasted text
284
229
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
285
230
  paste: true,
286
231
  sequence: pastedText,
@@ -302,12 +247,10 @@ describe('KeypressContext - Kitty Protocol', () => {
302
247
  const keyHandler = vi.fn();
303
248
  const wrapper = ({ children }) => (_jsx(KeypressProvider, { kittyProtocolEnabled: true, debugKeystrokeLogging: false, children: children }));
304
249
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
305
- act(() => {
306
- result.current.subscribe(keyHandler);
307
- });
250
+ act(() => result.current.subscribe(keyHandler));
308
251
  // Send a kitty sequence
309
252
  act(() => {
310
- stdin.sendKittySequence('\x1b[27u');
253
+ stdin.write('\x1b[27u');
311
254
  });
312
255
  expect(keyHandler).toHaveBeenCalled();
313
256
  expect(consoleLogSpy).not.toHaveBeenCalledWith(expect.stringContaining('[DEBUG] Kitty'));
@@ -316,16 +259,12 @@ describe('KeypressContext - Kitty Protocol', () => {
316
259
  const keyHandler = vi.fn();
317
260
  const wrapper = ({ children }) => (_jsx(KeypressProvider, { kittyProtocolEnabled: true, debugKeystrokeLogging: true, children: children }));
318
261
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
319
- act(() => {
320
- result.current.subscribe(keyHandler);
321
- });
262
+ act(() => result.current.subscribe(keyHandler));
322
263
  // Send a complete kitty sequence for escape
323
- act(() => {
324
- stdin.sendKittySequence('\x1b[27u');
325
- });
326
- expect(consoleLogSpy).toHaveBeenCalledWith('[DEBUG] Kitty buffer accumulating:', expect.stringContaining('"\\u001b[27u"'));
264
+ act(() => stdin.write('\x1b[27u'));
265
+ expect(consoleLogSpy).toHaveBeenCalledWith('[DEBUG] Input buffer accumulating:', expect.stringContaining('"\\u001b[27u"'));
327
266
  const parsedCall = consoleLogSpy.mock.calls.find((args) => typeof args[0] === 'string' &&
328
- args[0].includes('[DEBUG] Kitty sequence parsed successfully'));
267
+ args[0].includes('[DEBUG] Sequence parsed successfully'));
329
268
  expect(parsedCall).toBeTruthy();
330
269
  expect(parsedCall?.[1]).toEqual(expect.stringContaining('\\u001b[27u'));
331
270
  });
@@ -333,44 +272,21 @@ describe('KeypressContext - Kitty Protocol', () => {
333
272
  const keyHandler = vi.fn();
334
273
  const wrapper = ({ children }) => (_jsx(KeypressProvider, { kittyProtocolEnabled: true, debugKeystrokeLogging: true, children: children }));
335
274
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
336
- act(() => {
337
- result.current.subscribe(keyHandler);
338
- });
275
+ act(() => result.current.subscribe(keyHandler));
339
276
  // Send a long sequence starting with a valid kitty prefix to trigger overflow
340
277
  const longSequence = '\x1b[1;' + '1'.repeat(100);
341
- act(() => {
342
- stdin.sendKittySequence(longSequence);
343
- });
344
- expect(consoleLogSpy).toHaveBeenCalledWith('[DEBUG] Kitty buffer overflow, clearing:', expect.any(String));
278
+ act(() => stdin.write(longSequence));
279
+ expect(consoleLogSpy).toHaveBeenCalledWith('[DEBUG] Input buffer overflow, clearing:', expect.any(String));
345
280
  });
346
281
  it('should log kitty buffer clear on Ctrl+C when debugKeystrokeLogging is true', async () => {
347
282
  const keyHandler = vi.fn();
348
283
  const wrapper = ({ children }) => (_jsx(KeypressProvider, { kittyProtocolEnabled: true, debugKeystrokeLogging: true, children: children }));
349
284
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
350
- act(() => {
351
- result.current.subscribe(keyHandler);
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
- });
285
+ act(() => result.current.subscribe(keyHandler));
286
+ act(() => stdin.write(INCOMPLETE_KITTY_SEQUENCE));
363
287
  // Send Ctrl+C
364
- act(() => {
365
- stdin.pressKey({
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');
288
+ act(() => stdin.write('\x03'));
289
+ expect(consoleLogSpy).toHaveBeenCalledWith('[DEBUG] Input buffer cleared on Ctrl+C:', INCOMPLETE_KITTY_SEQUENCE);
374
290
  // Verify Ctrl+C was handled
375
291
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
376
292
  name: 'c',
@@ -381,24 +297,13 @@ describe('KeypressContext - Kitty Protocol', () => {
381
297
  const keyHandler = vi.fn();
382
298
  const wrapper = ({ children }) => (_jsx(KeypressProvider, { kittyProtocolEnabled: true, debugKeystrokeLogging: true, children: children }));
383
299
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
384
- act(() => {
385
- result.current.subscribe(keyHandler);
386
- });
300
+ act(() => result.current.subscribe(keyHandler));
387
301
  // Send incomplete kitty sequence
388
- const sequence = '\x1b[12';
389
- act(() => {
390
- stdin.pressKey({
391
- name: undefined,
392
- ctrl: false,
393
- meta: false,
394
- shift: false,
395
- sequence,
396
- });
397
- });
302
+ act(() => stdin.write(INCOMPLETE_KITTY_SEQUENCE));
398
303
  // Verify debug logging for accumulation
399
- expect(consoleLogSpy).toHaveBeenCalledWith('[DEBUG] Kitty buffer accumulating:', JSON.stringify(sequence));
304
+ expect(consoleLogSpy).toHaveBeenCalledWith('[DEBUG] Input buffer accumulating:', JSON.stringify(INCOMPLETE_KITTY_SEQUENCE));
400
305
  // Verify warning for char codes
401
- expect(consoleWarnSpy).toHaveBeenCalledWith('Kitty sequence buffer has content:', JSON.stringify(sequence));
306
+ expect(consoleWarnSpy).toHaveBeenCalledWith('Input sequence buffer has content:', JSON.stringify(INCOMPLETE_KITTY_SEQUENCE));
402
307
  });
403
308
  });
404
309
  describe('Parameterized functional keys', () => {
@@ -414,6 +319,13 @@ describe('KeypressContext - Kitty Protocol', () => {
414
319
  { sequence: `\x1b[1~`, expected: { name: 'home' } },
415
320
  { sequence: `\x1b[4~`, expected: { name: 'end' } },
416
321
  { sequence: `\x1b[2~`, expected: { name: 'insert' } },
322
+ { sequence: `\x1b[11~`, expected: { name: 'f1' } },
323
+ { sequence: `\x1b[17~`, expected: { name: 'f6' } },
324
+ { sequence: `\x1b[23~`, expected: { name: 'f11' } },
325
+ { sequence: `\x1b[24~`, expected: { name: 'f12' } },
326
+ // Reverse tabs
327
+ { sequence: `\x1b[Z`, expected: { name: 'tab', shift: true } },
328
+ { sequence: `\x1b[1;2Z`, expected: { name: 'tab', shift: true } },
417
329
  // Legacy Arrows
418
330
  {
419
331
  sequence: `\x1b[A`,
@@ -444,56 +356,32 @@ describe('KeypressContext - Kitty Protocol', () => {
444
356
  const keyHandler = vi.fn();
445
357
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
446
358
  act(() => result.current.subscribe(keyHandler));
447
- act(() => stdin.sendKittySequence(sequence));
359
+ act(() => stdin.write(sequence));
448
360
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining(expected));
449
361
  });
450
362
  });
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
363
  describe('Double-tap and batching', () => {
464
364
  it('should emit two delete events for double-tap CSI[3~', async () => {
465
- const keyHandler = vi.fn();
466
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
467
- act(() => result.current.subscribe(keyHandler));
468
- act(() => stdin.sendKittySequence(`\x1b[3~`));
469
- act(() => stdin.sendKittySequence(`\x1b[3~`));
365
+ const { keyHandler } = setupKeypressTest(true);
366
+ act(() => stdin.write(`\x1b[3~`));
367
+ act(() => stdin.write(`\x1b[3~`));
470
368
  expect(keyHandler).toHaveBeenNthCalledWith(1, expect.objectContaining({ name: 'delete' }));
471
369
  expect(keyHandler).toHaveBeenNthCalledWith(2, expect.objectContaining({ name: 'delete' }));
472
370
  });
473
371
  it('should parse two concatenated tilde-coded sequences in one chunk', async () => {
474
- const keyHandler = vi.fn();
475
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
476
- act(() => result.current.subscribe(keyHandler));
477
- act(() => stdin.sendKittySequence(`\x1b[3~\x1b[5~`));
372
+ const { keyHandler } = setupKeypressTest(true);
373
+ act(() => stdin.write(`\x1b[3~\x1b[5~`));
478
374
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({ name: 'delete' }));
479
375
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({ name: 'pageup' }));
480
376
  });
481
377
  it('should ignore incomplete CSI then parse the next complete sequence', async () => {
482
- const keyHandler = vi.fn();
483
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
484
- act(() => result.current.subscribe(keyHandler));
378
+ const { keyHandler } = setupKeypressTest(true);
485
379
  // Incomplete ESC sequence then a complete Delete
486
380
  act(() => {
487
381
  // Provide an incomplete ESC sequence chunk with a real ESC character
488
- stdin.pressKey({
489
- name: undefined,
490
- ctrl: false,
491
- meta: false,
492
- shift: false,
493
- sequence: '\x1b[1;',
494
- });
382
+ stdin.write('\x1b[1;');
495
383
  });
496
- act(() => stdin.sendKittySequence(`\x1b[3~`));
384
+ act(() => stdin.write(`\x1b[3~`));
497
385
  expect(keyHandler).toHaveBeenCalledTimes(1);
498
386
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({ name: 'delete' }));
499
387
  });
@@ -516,154 +404,45 @@ describe('Drag and Drop Handling', () => {
516
404
  vi.useRealTimers();
517
405
  });
518
406
  describe('drag start by quotes', () => {
519
- it('should start collecting when single quote arrives and not broadcast immediately', async () => {
520
- const keyHandler = vi.fn();
521
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
522
- act(() => {
523
- result.current.subscribe(keyHandler);
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
- });
535
- expect(keyHandler).not.toHaveBeenCalled();
536
- });
537
- it('should start collecting when double quote arrives and not broadcast immediately', async () => {
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 }) => {
538
411
  const keyHandler = vi.fn();
539
412
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
540
- act(() => {
541
- result.current.subscribe(keyHandler);
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
- });
413
+ act(() => result.current.subscribe(keyHandler));
414
+ act(() => stdin.write(quote));
553
415
  expect(keyHandler).not.toHaveBeenCalled();
554
416
  });
555
417
  });
556
418
  describe('drag collection and completion', () => {
557
- it('should collect single character inputs during drag mode', async () => {
558
- const keyHandler = vi.fn();
559
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
560
- act(() => {
561
- result.current.subscribe(keyHandler);
562
- });
563
- // 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
- });
574
- // 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
- });
585
- // Character should not be immediately broadcast
586
- expect(keyHandler).not.toHaveBeenCalled();
587
- // Fast-forward to completion timeout
588
- act(() => {
589
- vi.advanceTimersByTime(DRAG_COMPLETION_TIMEOUT_MS + 10);
590
- });
591
- // Should broadcast the collected path as paste (includes starting quote)
592
- expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
593
- name: '',
594
- paste: true,
595
- sequence: `${SINGLE_QUOTE}a`,
596
- }));
597
- });
598
- it('should collect multiple characters and complete on timeout', async () => {
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 }) => {
599
431
  const keyHandler = vi.fn();
600
432
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
601
- act(() => {
602
- result.current.subscribe(keyHandler);
603
- });
604
- // 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
- });
615
- // Send multiple characters
616
- act(() => {
617
- stdin.pressKey({
618
- name: undefined,
619
- ctrl: false,
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
- });
433
+ act(() => result.current.subscribe(keyHandler));
434
+ act(() => stdin.write(SINGLE_QUOTE));
435
+ characters.forEach((char) => {
436
+ act(() => stdin.write(char));
655
437
  });
656
- // Characters should not be immediately broadcast
657
438
  expect(keyHandler).not.toHaveBeenCalled();
658
- // Fast-forward to completion timeout
659
439
  act(() => {
660
440
  vi.advanceTimersByTime(DRAG_COMPLETION_TIMEOUT_MS + 10);
661
441
  });
662
- // Should broadcast the collected path as paste (includes starting quote)
663
442
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
664
443
  name: '',
665
444
  paste: true,
666
- sequence: `${SINGLE_QUOTE}path`,
445
+ sequence: `${SINGLE_QUOTE}${expectedText}`,
667
446
  }));
668
447
  });
669
448
  });
@@ -684,94 +463,91 @@ describe('Kitty Sequence Parsing', () => {
684
463
  afterEach(() => {
685
464
  vi.useRealTimers();
686
465
  });
687
- // Terminals to test
688
- const terminals = ['iTerm2', 'Ghostty', 'MacTerminal', 'VSCodeTerminal'];
689
- // Key mappings: letter -> [keycode, accented character, shouldHaveMeta]
690
- // Note: µ (mu) is sent with meta:false on iTerm2/VSCode
691
- const keys = {
692
- a: [97, 'å', true],
693
- o: [111, 'ø', true],
694
- m: [109, 'µ', false],
695
- };
696
- it.each(terminals.flatMap((terminal) => Object.entries(keys).map(([key, [keycode, accentedChar, shouldHaveMeta]]) => {
697
- if (terminal === 'Ghostty') {
698
- // Ghostty uses kitty protocol sequences
699
- return {
700
- terminal,
701
- key,
702
- kittySequence: `\x1b[${keycode};3u`,
703
- expected: {
704
- name: key,
705
- ctrl: false,
706
- meta: true,
707
- shift: false,
708
- paste: false,
709
- kittyProtocol: true,
710
- },
711
- };
712
- }
713
- else if (terminal === 'MacTerminal') {
714
- // Mac Terminal sends ESC + letter
715
- return {
716
- terminal,
717
- key,
718
- kitty: false,
719
- input: {
720
- sequence: `\x1b${key}`,
721
- name: key,
722
- ctrl: false,
723
- meta: true,
724
- shift: false,
725
- paste: false,
726
- },
727
- expected: {
728
- sequence: `\x1b${key}`,
729
- name: key,
730
- ctrl: false,
731
- meta: true,
732
- shift: false,
733
- paste: false,
734
- },
735
- };
736
- }
737
- else {
738
- // iTerm2 and VSCode send accented characters (å, ø, µ)
739
- // Note: µ comes with meta:false but gets converted to m with meta:true
740
- return {
741
- terminal,
742
- key,
743
- input: {
744
- name: key,
745
- ctrl: false,
746
- meta: shouldHaveMeta,
747
- shift: false,
748
- paste: false,
749
- sequence: accentedChar,
750
- },
751
- expected: {
752
- name: key,
753
- ctrl: false,
754
- meta: true, // Always expect meta:true after conversion
755
- shift: false,
756
- paste: false,
757
- sequence: accentedChar,
758
- },
759
- };
760
- }
761
- })))('should handle Alt+$key in $terminal', ({ kittySequence, input, expected, kitty = true, }) => {
762
- const keyHandler = vi.fn();
763
- const testWrapper = ({ children }) => (_jsx(KeypressProvider, { kittyProtocolEnabled: kitty, children: children }));
764
- const { result } = renderHook(() => useKeypressContext(), {
765
- wrapper: testWrapper,
466
+ describe('Cross-terminal Alt key handling (simulating macOS)', () => {
467
+ let originalPlatform;
468
+ beforeEach(() => {
469
+ originalPlatform = process.platform;
470
+ Object.defineProperty(process, 'platform', {
471
+ value: 'darwin',
472
+ configurable: true,
473
+ });
474
+ });
475
+ afterEach(() => {
476
+ Object.defineProperty(process, 'platform', {
477
+ value: originalPlatform,
478
+ configurable: true,
479
+ });
480
+ });
481
+ // Terminals to test
482
+ const terminals = ['iTerm2', 'Ghostty', 'MacTerminal', 'VSCodeTerminal'];
483
+ // Key mappings: letter -> [keycode, accented character]
484
+ const keys = {
485
+ b: [98, '\u222B'],
486
+ f: [102, '\u0192'],
487
+ m: [109, '\u00B5'],
488
+ };
489
+ it.each(terminals.flatMap((terminal) => Object.entries(keys).map(([key, [keycode, accentedChar]]) => {
490
+ if (terminal === 'Ghostty') {
491
+ // Ghostty uses kitty protocol sequences
492
+ return {
493
+ terminal,
494
+ key,
495
+ chunk: `\x1b[${keycode};3u`,
496
+ expected: {
497
+ name: key,
498
+ ctrl: false,
499
+ meta: true,
500
+ shift: false,
501
+ paste: false,
502
+ kittyProtocol: true,
503
+ },
504
+ };
505
+ }
506
+ else if (terminal === 'MacTerminal') {
507
+ // Mac Terminal sends ESC + letter
508
+ return {
509
+ terminal,
510
+ key,
511
+ kitty: false,
512
+ chunk: `\x1b${key}`,
513
+ expected: {
514
+ sequence: `\x1b${key}`,
515
+ name: key,
516
+ ctrl: false,
517
+ meta: true,
518
+ shift: false,
519
+ paste: false,
520
+ },
521
+ };
522
+ }
523
+ else {
524
+ // iTerm2 and VSCode send accented characters (å, ø, µ)
525
+ // Note: µ (mu) is sent with meta:false on iTerm2/VSCode but
526
+ // gets converted to m with meta:true
527
+ return {
528
+ terminal,
529
+ key,
530
+ chunk: accentedChar,
531
+ expected: {
532
+ name: key,
533
+ ctrl: false,
534
+ meta: true, // Always expect meta:true after conversion
535
+ shift: false,
536
+ paste: false,
537
+ sequence: accentedChar,
538
+ },
539
+ };
540
+ }
541
+ })))('should handle Alt+$key in $terminal', ({ chunk, expected, kitty = true, }) => {
542
+ const keyHandler = vi.fn();
543
+ const testWrapper = ({ children }) => (_jsx(KeypressProvider, { kittyProtocolEnabled: kitty, children: children }));
544
+ const { result } = renderHook(() => useKeypressContext(), {
545
+ wrapper: testWrapper,
546
+ });
547
+ act(() => result.current.subscribe(keyHandler));
548
+ act(() => stdin.write(chunk));
549
+ expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining(expected));
766
550
  });
767
- act(() => result.current.subscribe(keyHandler));
768
- if (kittySequence) {
769
- act(() => stdin.sendKittySequence(kittySequence));
770
- }
771
- else if (input) {
772
- act(() => stdin.pressKey(input));
773
- }
774
- expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining(expected));
775
551
  });
776
552
  describe('Backslash key handling', () => {
777
553
  beforeEach(() => {
@@ -781,17 +557,8 @@ describe('Kitty Sequence Parsing', () => {
781
557
  vi.useRealTimers();
782
558
  });
783
559
  it('should treat backslash as a regular keystroke', () => {
784
- const keyHandler = vi.fn();
785
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
786
- act(() => result.current.subscribe(keyHandler));
787
- act(() => stdin.pressKey({
788
- name: undefined,
789
- ctrl: false,
790
- meta: false,
791
- shift: false,
792
- paste: false,
793
- sequence: '\\',
794
- }));
560
+ const { keyHandler } = setupKeypressTest(true);
561
+ act(() => stdin.write('\\'));
795
562
  // Advance timers to trigger the backslash timeout
796
563
  act(() => {
797
564
  vi.runAllTimers();
@@ -805,60 +572,32 @@ describe('Kitty Sequence Parsing', () => {
805
572
  it('should timeout and flush incomplete kitty sequences after 50ms', async () => {
806
573
  const keyHandler = vi.fn();
807
574
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
808
- act(() => {
809
- result.current.subscribe(keyHandler);
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
- });
575
+ act(() => result.current.subscribe(keyHandler));
576
+ act(() => stdin.write(INCOMPLETE_KITTY_SEQUENCE));
822
577
  // Should not broadcast immediately
823
578
  expect(keyHandler).not.toHaveBeenCalled();
824
579
  // Advance time just before timeout
825
- act(() => {
826
- vi.advanceTimersByTime(KITTY_SEQUENCE_TIMEOUT_MS - 5);
827
- });
580
+ act(() => vi.advanceTimersByTime(KITTY_SEQUENCE_TIMEOUT_MS - 5));
828
581
  // Still shouldn't broadcast
829
582
  expect(keyHandler).not.toHaveBeenCalled();
830
583
  // Advance past timeout
831
- act(() => {
832
- vi.advanceTimersByTime(10);
833
- });
584
+ act(() => vi.advanceTimersByTime(10));
834
585
  // Should now broadcast the incomplete sequence as regular input
835
586
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
836
587
  name: '',
837
- sequence: '\x1b[1;',
588
+ sequence: INCOMPLETE_KITTY_SEQUENCE,
838
589
  paste: false,
839
590
  }));
840
591
  });
841
592
  it('should immediately flush non-kitty CSI sequences', async () => {
842
593
  const keyHandler = vi.fn();
843
594
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
844
- act(() => {
845
- result.current.subscribe(keyHandler);
846
- });
595
+ act(() => result.current.subscribe(keyHandler));
847
596
  // Send a CSI sequence that doesn't match kitty patterns
848
597
  // 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
- });
598
+ act(() => stdin.write('\x1b[m'));
859
599
  // Should broadcast immediately as it's not a valid kitty pattern
860
600
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
861
- name: '',
862
601
  sequence: '\x1b[m',
863
602
  paste: false,
864
603
  }));
@@ -866,20 +605,9 @@ describe('Kitty Sequence Parsing', () => {
866
605
  it('should parse valid kitty sequences immediately when complete', async () => {
867
606
  const keyHandler = vi.fn();
868
607
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
869
- act(() => {
870
- result.current.subscribe(keyHandler);
871
- });
608
+ act(() => result.current.subscribe(keyHandler));
872
609
  // 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
- });
610
+ act(() => stdin.write('\x1b[97;5u'));
883
611
  // Should parse and broadcast immediately
884
612
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
885
613
  name: 'a',
@@ -890,20 +618,9 @@ describe('Kitty Sequence Parsing', () => {
890
618
  it('should handle batched kitty sequences correctly', async () => {
891
619
  const keyHandler = vi.fn();
892
620
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
893
- act(() => {
894
- result.current.subscribe(keyHandler);
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
- });
621
+ act(() => result.current.subscribe(keyHandler));
622
+ // Send Ctrl+a followed by Ctrl+b
623
+ act(() => stdin.write('\x1b[97;5u\x1b[98;5u'));
907
624
  // Should parse both sequences
908
625
  expect(keyHandler).toHaveBeenCalledTimes(2);
909
626
  expect(keyHandler).toHaveBeenNthCalledWith(1, expect.objectContaining({
@@ -920,35 +637,12 @@ describe('Kitty Sequence Parsing', () => {
920
637
  it('should clear kitty buffer and timeout on Ctrl+C', async () => {
921
638
  const keyHandler = vi.fn();
922
639
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
923
- act(() => {
924
- result.current.subscribe(keyHandler);
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
- });
640
+ act(() => result.current.subscribe(keyHandler));
641
+ act(() => stdin.write(INCOMPLETE_KITTY_SEQUENCE));
937
642
  // 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
- });
643
+ act(() => stdin.write('\x03'));
948
644
  // Advance past timeout
949
- act(() => {
950
- vi.advanceTimersByTime(KITTY_SEQUENCE_TIMEOUT_MS + 10);
951
- });
645
+ act(() => vi.advanceTimersByTime(KITTY_SEQUENCE_TIMEOUT_MS + 10));
952
646
  // Should only have received Ctrl+C, not the incomplete sequence
953
647
  expect(keyHandler).toHaveBeenCalledTimes(1);
954
648
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
@@ -959,20 +653,10 @@ describe('Kitty Sequence Parsing', () => {
959
653
  it('should handle mixed valid and invalid sequences', async () => {
960
654
  const keyHandler = vi.fn();
961
655
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
962
- act(() => {
963
- result.current.subscribe(keyHandler);
964
- });
656
+ act(() => result.current.subscribe(keyHandler));
965
657
  // Send valid kitty sequence followed by invalid CSI
966
- act(() => {
967
- stdin.pressKey({
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
- });
658
+ // Valid enter, then invalid sequence
659
+ act(() => stdin.write('\x1b[13u\x1b[!'));
976
660
  // Should parse valid sequence and flush invalid immediately
977
661
  expect(keyHandler).toHaveBeenCalledTimes(2);
978
662
  expect(keyHandler).toHaveBeenNthCalledWith(1, expect.objectContaining({
@@ -980,7 +664,6 @@ describe('Kitty Sequence Parsing', () => {
980
664
  kittyProtocol: true,
981
665
  }));
982
666
  expect(keyHandler).toHaveBeenNthCalledWith(2, expect.objectContaining({
983
- name: '',
984
667
  sequence: '\x1b[!',
985
668
  }));
986
669
  });
@@ -989,20 +672,9 @@ describe('Kitty Sequence Parsing', () => {
989
672
  const { result } = renderHook(() => useKeypressContext(), {
990
673
  wrapper: ({ children }) => wrapper({ children, kittyProtocolEnabled: false }),
991
674
  });
992
- act(() => {
993
- result.current.subscribe(keyHandler);
994
- });
675
+ act(() => result.current.subscribe(keyHandler));
995
676
  // 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
- });
677
+ act(() => stdin.write('\x1b[13u'));
1006
678
  // Should pass through without parsing
1007
679
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
1008
680
  sequence: '\x1b[13u',
@@ -1025,7 +697,7 @@ describe('Kitty Sequence Parsing', () => {
1025
697
  act(() => {
1026
698
  stdin.emit('data', Buffer.from(char));
1027
699
  });
1028
- await new Promise((resolve) => setTimeout(resolve, 0));
700
+ await new Promise((resolve) => setImmediate(resolve));
1029
701
  }
1030
702
  // Should parse once complete
1031
703
  await waitFor(() => {
@@ -1038,113 +710,56 @@ describe('Kitty Sequence Parsing', () => {
1038
710
  it('should reset timeout when new input arrives', async () => {
1039
711
  const keyHandler = vi.fn();
1040
712
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
1041
- act(() => {
1042
- result.current.subscribe(keyHandler);
1043
- });
713
+ act(() => result.current.subscribe(keyHandler));
1044
714
  // 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
- });
715
+ act(() => stdin.write('\x1b[97;13'));
1055
716
  // Advance time partway
1056
- act(() => {
1057
- vi.advanceTimersByTime(30);
1058
- });
717
+ act(() => vi.advanceTimersByTime(30));
1059
718
  // 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
- });
719
+ act(() => stdin.write('5'));
1070
720
  // Advance time from the first timeout point
1071
- act(() => {
1072
- vi.advanceTimersByTime(25);
1073
- });
721
+ act(() => vi.advanceTimersByTime(25));
1074
722
  // Should not have timed out yet (timeout restarted)
1075
723
  expect(keyHandler).not.toHaveBeenCalled();
1076
724
  // 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
- });
725
+ act(() => stdin.write('u'));
1087
726
  // Should now parse as complete enter key
1088
727
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
1089
- name: 'return',
728
+ name: 'a',
1090
729
  kittyProtocol: true,
1091
730
  }));
1092
731
  });
1093
732
  it('should flush incomplete kitty sequence on FOCUS_IN event', async () => {
1094
733
  const keyHandler = vi.fn();
1095
734
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
1096
- act(() => {
1097
- result.current.subscribe(keyHandler);
1098
- });
1099
- // Send incomplete kitty sequence
1100
- act(() => {
1101
- stdin.pressKey({
1102
- sequence: '\x1b[1;',
1103
- });
1104
- });
735
+ act(() => result.current.subscribe(keyHandler));
736
+ act(() => stdin.write(INCOMPLETE_KITTY_SEQUENCE));
1105
737
  // Incomplete sequence should be buffered, not broadcast
1106
738
  expect(keyHandler).not.toHaveBeenCalled();
1107
739
  // Send FOCUS_IN event
1108
- const FOCUS_IN = '\x1b[I';
1109
- act(() => {
1110
- stdin.pressKey({
1111
- sequence: FOCUS_IN,
1112
- });
1113
- });
740
+ act(() => stdin.write('\x1b[I'));
1114
741
  // The buffered sequence should be flushed
1115
742
  expect(keyHandler).toHaveBeenCalledTimes(1);
1116
743
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
1117
744
  name: '',
1118
- sequence: '\x1b[1;',
745
+ sequence: INCOMPLETE_KITTY_SEQUENCE,
1119
746
  paste: false,
1120
747
  }));
1121
748
  });
1122
749
  it('should flush incomplete kitty sequence on FOCUS_OUT event', async () => {
1123
750
  const keyHandler = vi.fn();
1124
751
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
1125
- act(() => {
1126
- result.current.subscribe(keyHandler);
1127
- });
1128
- // Send incomplete kitty sequence
1129
- act(() => {
1130
- stdin.pressKey({
1131
- sequence: '\x1b[1;',
1132
- });
1133
- });
752
+ act(() => result.current.subscribe(keyHandler));
753
+ act(() => stdin.write(INCOMPLETE_KITTY_SEQUENCE));
1134
754
  // Incomplete sequence should be buffered, not broadcast
1135
755
  expect(keyHandler).not.toHaveBeenCalled();
1136
756
  // Send FOCUS_OUT event
1137
- const FOCUS_OUT = '\x1b[O';
1138
- act(() => {
1139
- stdin.pressKey({
1140
- sequence: FOCUS_OUT,
1141
- });
1142
- });
757
+ act(() => stdin.write('\x1b[O'));
1143
758
  // The buffered sequence should be flushed
1144
759
  expect(keyHandler).toHaveBeenCalledTimes(1);
1145
760
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
1146
761
  name: '',
1147
- sequence: '\x1b[1;',
762
+ sequence: INCOMPLETE_KITTY_SEQUENCE,
1148
763
  paste: false,
1149
764
  }));
1150
765
  });
@@ -1152,39 +767,27 @@ describe('Kitty Sequence Parsing', () => {
1152
767
  vi.useFakeTimers();
1153
768
  const keyHandler = vi.fn();
1154
769
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
1155
- act(() => {
1156
- result.current.subscribe(keyHandler);
1157
- });
1158
- // Send incomplete kitty sequence
1159
- act(() => {
1160
- stdin.pressKey({
1161
- sequence: '\x1b[1;',
1162
- });
1163
- });
770
+ act(() => result.current.subscribe(keyHandler));
771
+ act(() => stdin.write(INCOMPLETE_KITTY_SEQUENCE));
1164
772
  // Incomplete sequence should be buffered, not broadcast
1165
773
  expect(keyHandler).not.toHaveBeenCalled();
1166
774
  // Send paste start sequence
1167
- const PASTE_MODE_PREFIX = `\x1b[200~`;
1168
- act(() => {
1169
- stdin.emit('data', Buffer.from(PASTE_MODE_PREFIX));
1170
- });
775
+ act(() => stdin.write(`\x1b[200~`));
1171
776
  // The buffered sequence should be flushed
1172
777
  expect(keyHandler).toHaveBeenCalledTimes(1);
1173
778
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
1174
779
  name: '',
1175
- sequence: '\x1b[1;',
780
+ sequence: INCOMPLETE_KITTY_SEQUENCE,
1176
781
  paste: false,
1177
782
  }));
1178
783
  // Now send some paste content and end paste to make sure paste still works
1179
784
  const pastedText = 'hello';
1180
785
  const PASTE_MODE_SUFFIX = `\x1b[201~`;
1181
786
  act(() => {
1182
- stdin.emit('data', Buffer.from(pastedText));
1183
- stdin.emit('data', Buffer.from(PASTE_MODE_SUFFIX));
1184
- });
1185
- act(() => {
1186
- vi.runAllTimers();
787
+ stdin.write(pastedText);
788
+ stdin.write(PASTE_MODE_SUFFIX);
1187
789
  });
790
+ act(() => vi.runAllTimers());
1188
791
  // The paste event should be broadcast
1189
792
  expect(keyHandler).toHaveBeenCalledTimes(2);
1190
793
  expect(keyHandler).toHaveBeenNthCalledWith(2, expect.objectContaining({
@@ -1193,5 +796,122 @@ describe('Kitty Sequence Parsing', () => {
1193
796
  }));
1194
797
  vi.useRealTimers();
1195
798
  });
799
+ describe('SGR Mouse Handling', () => {
800
+ it('should ignore SGR mouse sequences', async () => {
801
+ const keyHandler = vi.fn();
802
+ const { result } = renderHook(() => useKeypressContext(), { wrapper });
803
+ act(() => result.current.subscribe(keyHandler));
804
+ // Send various SGR mouse sequences
805
+ act(() => {
806
+ stdin.write('\x1b[<0;10;20M'); // Mouse press
807
+ stdin.write('\x1b[<0;10;20m'); // Mouse release
808
+ stdin.write('\x1b[<32;30;40M'); // Mouse drag
809
+ stdin.write('\x1b[<64;5;5M'); // Scroll up
810
+ });
811
+ // Should not broadcast any of these as keystrokes
812
+ expect(keyHandler).not.toHaveBeenCalled();
813
+ });
814
+ it('should handle mixed SGR mouse and key sequences', async () => {
815
+ const keyHandler = vi.fn();
816
+ const { result } = renderHook(() => useKeypressContext(), { wrapper });
817
+ act(() => result.current.subscribe(keyHandler));
818
+ // Send mouse event then a key press
819
+ act(() => {
820
+ stdin.write('\x1b[<0;10;20M');
821
+ stdin.write('a');
822
+ });
823
+ // Should only broadcast 'a'
824
+ expect(keyHandler).toHaveBeenCalledTimes(1);
825
+ expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
826
+ name: 'a',
827
+ sequence: 'a',
828
+ }));
829
+ });
830
+ it('should ignore X11 mouse sequences', async () => {
831
+ const keyHandler = vi.fn();
832
+ const { result } = renderHook(() => useKeypressContext(), { wrapper });
833
+ act(() => result.current.subscribe(keyHandler));
834
+ // Send X11 mouse sequence: ESC [ M followed by 3 bytes
835
+ // Space is 32. 32+0=32 (button 0), 32+33=65 ('A', col 33), 32+34=66 ('B', row 34)
836
+ const x11Seq = '\x1b[M AB';
837
+ act(() => {
838
+ stdin.write(x11Seq);
839
+ });
840
+ // Should not broadcast as keystrokes
841
+ expect(keyHandler).not.toHaveBeenCalled();
842
+ });
843
+ it('should not flush slow SGR mouse sequences as garbage', async () => {
844
+ vi.useFakeTimers();
845
+ const keyHandler = vi.fn();
846
+ const { result } = renderHook(() => useKeypressContext(), { wrapper });
847
+ act(() => result.current.subscribe(keyHandler));
848
+ // Send start of SGR sequence
849
+ act(() => stdin.write('\x1b[<'));
850
+ // Advance time past the normal kitty timeout (50ms)
851
+ act(() => vi.advanceTimersByTime(KITTY_SEQUENCE_TIMEOUT_MS + 10));
852
+ // Send the rest
853
+ act(() => stdin.write('0;37;25M'));
854
+ // Should NOT have flushed the prefix as garbage, and should have consumed the whole thing
855
+ expect(keyHandler).not.toHaveBeenCalled();
856
+ vi.useRealTimers();
857
+ });
858
+ it('should ignore specific SGR mouse sequence sandwiched between keystrokes', async () => {
859
+ const keyHandler = vi.fn();
860
+ const { result } = renderHook(() => useKeypressContext(), { wrapper });
861
+ act(() => result.current.subscribe(keyHandler));
862
+ act(() => {
863
+ stdin.write('H');
864
+ stdin.write('\x1b[<64;96;8M');
865
+ stdin.write('I');
866
+ });
867
+ expect(keyHandler).toHaveBeenCalledTimes(2);
868
+ expect(keyHandler).toHaveBeenNthCalledWith(1, expect.objectContaining({ name: 'h', sequence: 'H', shift: true }));
869
+ expect(keyHandler).toHaveBeenNthCalledWith(2, expect.objectContaining({ name: 'i', sequence: 'I', shift: true }));
870
+ });
871
+ });
872
+ 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();
903
+ });
904
+ });
905
+ it('should handle F12 when kittyProtocolEnabled is false', async () => {
906
+ const keyHandler = vi.fn();
907
+ const wrapper = ({ children }) => (_jsx(KeypressProvider, { kittyProtocolEnabled: false, children: children }));
908
+ const { result } = renderHook(() => useKeypressContext(), { wrapper });
909
+ act(() => result.current.subscribe(keyHandler));
910
+ act(() => {
911
+ stdin.write('\u001b[24~');
912
+ });
913
+ expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({ name: 'f12', sequence: '\u001b[24~' }));
914
+ });
915
+ });
1196
916
  });
1197
917
  //# sourceMappingURL=KeypressContext.test.js.map