fss-link 1.0.40 → 1.0.45

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 (428) hide show
  1. package/dist/commands/mcp/add.test.ts +122 -0
  2. package/dist/commands/mcp/add.ts +222 -0
  3. package/dist/commands/mcp/list.test.ts +154 -0
  4. package/dist/commands/mcp/list.ts +139 -0
  5. package/dist/commands/mcp/remove.test.ts +69 -0
  6. package/dist/commands/mcp/remove.ts +60 -0
  7. package/dist/commands/mcp.test.ts +55 -0
  8. package/dist/commands/mcp.ts +27 -0
  9. package/dist/config/apiValidation.test.ts +118 -0
  10. package/dist/config/auth.test.ts +79 -0
  11. package/dist/config/auth.ts +100 -0
  12. package/dist/config/config.integration.test.ts +407 -0
  13. package/dist/config/config.test.ts +1952 -0
  14. package/dist/config/config.ts +690 -0
  15. package/dist/config/database.test.ts +96 -0
  16. package/dist/config/database.ts +752 -0
  17. package/dist/config/extension.test.ts +236 -0
  18. package/dist/config/extension.ts +180 -0
  19. package/dist/config/keyBindings.test.ts +62 -0
  20. package/dist/config/keyBindings.ts +184 -0
  21. package/dist/config/modelManager.ts +275 -0
  22. package/dist/config/providerManager.ts +244 -0
  23. package/dist/config/providerPersistence.test.ts +377 -0
  24. package/dist/config/providerPersistence.ts +105 -0
  25. package/dist/config/sandboxConfig.ts +107 -0
  26. package/dist/config/settings.test.ts +1424 -0
  27. package/dist/config/settings.ts +517 -0
  28. package/dist/config/settingsSchema.test.ts +252 -0
  29. package/dist/config/settingsSchema.ts +728 -0
  30. package/dist/config/trustedFolders.test.ts +208 -0
  31. package/dist/config/trustedFolders.ts +167 -0
  32. package/dist/gemini.test.tsx +252 -0
  33. package/dist/gemini.tsx +357 -0
  34. package/dist/generated/git-commit.ts +10 -0
  35. package/dist/index.ts +21 -0
  36. package/dist/nonInteractiveCli.test.ts +276 -0
  37. package/dist/nonInteractiveCli.ts +143 -0
  38. package/dist/package.json +87 -87
  39. package/dist/patches/is-in-ci.ts +17 -0
  40. package/dist/services/BuiltinCommandLoader.test.ts +127 -0
  41. package/dist/services/BuiltinCommandLoader.ts +95 -0
  42. package/dist/services/CommandService.test.ts +352 -0
  43. package/dist/services/CommandService.ts +103 -0
  44. package/dist/services/FileCommandLoader.test.ts +1002 -0
  45. package/dist/services/FileCommandLoader.ts +289 -0
  46. package/dist/services/McpPromptLoader.ts +231 -0
  47. package/dist/services/SearchEngineConfigProvider.ts +100 -0
  48. package/dist/services/prompt-processors/argumentProcessor.test.ts +41 -0
  49. package/dist/services/prompt-processors/argumentProcessor.ts +23 -0
  50. package/dist/services/prompt-processors/shellProcessor.test.ts +709 -0
  51. package/dist/services/prompt-processors/shellProcessor.ts +248 -0
  52. package/dist/services/prompt-processors/types.ts +44 -0
  53. package/dist/services/types.ts +24 -0
  54. package/dist/src/config/apiValidation.test.d.ts +6 -0
  55. package/dist/src/config/apiValidation.test.js +99 -0
  56. package/dist/src/config/apiValidation.test.js.map +1 -0
  57. package/dist/src/config/database.d.ts +32 -0
  58. package/dist/src/config/database.js +281 -2
  59. package/dist/src/config/database.js.map +1 -1
  60. package/dist/src/config/database.test.d.ts +6 -0
  61. package/dist/src/config/database.test.js +80 -0
  62. package/dist/src/config/database.test.js.map +1 -0
  63. package/dist/src/config/providerManager.d.ts +74 -0
  64. package/dist/src/config/providerManager.js +203 -0
  65. package/dist/src/config/providerManager.js.map +1 -0
  66. package/dist/src/config/providerPersistence.d.ts +75 -0
  67. package/dist/src/config/providerPersistence.js +55 -0
  68. package/dist/src/config/providerPersistence.js.map +1 -0
  69. package/dist/src/config/providerPersistence.test.d.ts +6 -0
  70. package/dist/src/config/providerPersistence.test.js +283 -0
  71. package/dist/src/config/providerPersistence.test.js.map +1 -0
  72. package/dist/src/config/settingsSchema.d.ts +9 -0
  73. package/dist/src/config/settingsSchema.js +9 -0
  74. package/dist/src/config/settingsSchema.js.map +1 -1
  75. package/dist/src/generated/git-commit.d.ts +1 -1
  76. package/dist/src/generated/git-commit.js +1 -1
  77. package/dist/src/services/BuiltinCommandLoader.js +2 -0
  78. package/dist/src/services/BuiltinCommandLoader.js.map +1 -1
  79. package/dist/src/ui/App.js +14 -2
  80. package/dist/src/ui/App.js.map +1 -1
  81. package/dist/src/ui/commands/contextCommand.d.ts +7 -0
  82. package/dist/src/ui/commands/contextCommand.js +115 -0
  83. package/dist/src/ui/commands/contextCommand.js.map +1 -0
  84. package/dist/src/ui/components/ContextUsageDisplay.d.ts +3 -1
  85. package/dist/src/ui/components/ContextUsageDisplay.js +43 -3
  86. package/dist/src/ui/components/ContextUsageDisplay.js.map +1 -1
  87. package/dist/src/ui/components/Footer.d.ts +1 -0
  88. package/dist/src/ui/components/Footer.js +2 -2
  89. package/dist/src/ui/components/Footer.js.map +1 -1
  90. package/dist/src/ui/components/GeminiKeyDialog.d.ts +11 -0
  91. package/dist/src/ui/components/GeminiKeyDialog.js +156 -0
  92. package/dist/src/ui/components/GeminiKeyDialog.js.map +1 -0
  93. package/dist/src/ui/components/OpenAIEndpointDialog.d.ts +19 -0
  94. package/dist/src/ui/components/OpenAIEndpointDialog.js +163 -0
  95. package/dist/src/ui/components/OpenAIEndpointDialog.js.map +1 -0
  96. package/dist/src/ui/components/WelcomeBackDialog.d.ts +36 -0
  97. package/dist/src/ui/components/WelcomeBackDialog.js +109 -0
  98. package/dist/src/ui/components/WelcomeBackDialog.js.map +1 -0
  99. package/dist/src/ui/hooks/useWelcomeBack.d.ts +52 -0
  100. package/dist/src/ui/hooks/useWelcomeBack.js +214 -0
  101. package/dist/src/ui/hooks/useWelcomeBack.js.map +1 -0
  102. package/dist/src/zed-integration/schema.d.ts +1516 -1516
  103. package/dist/test-setup.ts +12 -0
  104. package/dist/test-utils/customMatchers.ts +65 -0
  105. package/dist/test-utils/mockCommandContext.test.ts +62 -0
  106. package/dist/test-utils/mockCommandContext.ts +105 -0
  107. package/dist/test-utils/render.tsx +18 -0
  108. package/dist/tsconfig.tsbuildinfo +1 -1
  109. package/dist/ui/App.test.tsx +2181 -0
  110. package/dist/ui/App.tsx +1344 -0
  111. package/dist/ui/IdeIntegrationNudge.tsx +98 -0
  112. package/dist/ui/__snapshots__/App.test.tsx.snap +124 -0
  113. package/dist/ui/colors.ts +56 -0
  114. package/dist/ui/commands/aboutCommand.test.ts +153 -0
  115. package/dist/ui/commands/aboutCommand.ts +49 -0
  116. package/dist/ui/commands/authCommand.test.ts +36 -0
  117. package/dist/ui/commands/authCommand.ts +17 -0
  118. package/dist/ui/commands/bugCommand.test.ts +114 -0
  119. package/dist/ui/commands/bugCommand.ts +92 -0
  120. package/dist/ui/commands/chatCommand.test.ts +414 -0
  121. package/dist/ui/commands/chatCommand.ts +280 -0
  122. package/dist/ui/commands/clearCommand.test.ts +100 -0
  123. package/dist/ui/commands/clearCommand.ts +29 -0
  124. package/dist/ui/commands/compressCommand.test.ts +129 -0
  125. package/dist/ui/commands/compressCommand.ts +78 -0
  126. package/dist/ui/commands/contextCommand.ts +132 -0
  127. package/dist/ui/commands/copyCommand.test.ts +296 -0
  128. package/dist/ui/commands/copyCommand.ts +67 -0
  129. package/dist/ui/commands/corgiCommand.test.ts +34 -0
  130. package/dist/ui/commands/corgiCommand.ts +16 -0
  131. package/dist/ui/commands/directoryCommand.test.tsx +185 -0
  132. package/dist/ui/commands/directoryCommand.tsx +179 -0
  133. package/dist/ui/commands/docsCommand.test.ts +99 -0
  134. package/dist/ui/commands/docsCommand.ts +42 -0
  135. package/dist/ui/commands/editorCommand.test.ts +30 -0
  136. package/dist/ui/commands/editorCommand.ts +21 -0
  137. package/dist/ui/commands/extensionsCommand.test.ts +67 -0
  138. package/dist/ui/commands/extensionsCommand.ts +46 -0
  139. package/dist/ui/commands/helpCommand.test.ts +52 -0
  140. package/dist/ui/commands/helpCommand.ts +23 -0
  141. package/dist/ui/commands/ideCommand.test.ts +255 -0
  142. package/dist/ui/commands/ideCommand.ts +283 -0
  143. package/dist/ui/commands/initCommand.test.ts +127 -0
  144. package/dist/ui/commands/initCommand.ts +117 -0
  145. package/dist/ui/commands/mcpCommand.test.ts +1057 -0
  146. package/dist/ui/commands/mcpCommand.ts +531 -0
  147. package/dist/ui/commands/memoryCommand.test.ts +344 -0
  148. package/dist/ui/commands/memoryCommand.ts +305 -0
  149. package/dist/ui/commands/privacyCommand.test.ts +38 -0
  150. package/dist/ui/commands/privacyCommand.ts +17 -0
  151. package/dist/ui/commands/quitCommand.test.ts +55 -0
  152. package/dist/ui/commands/quitCommand.ts +36 -0
  153. package/dist/ui/commands/restoreCommand.test.ts +250 -0
  154. package/dist/ui/commands/restoreCommand.ts +157 -0
  155. package/dist/ui/commands/searchEngineSetupCommand.ts +18 -0
  156. package/dist/ui/commands/settingsCommand.test.ts +36 -0
  157. package/dist/ui/commands/settingsCommand.ts +17 -0
  158. package/dist/ui/commands/setupGithubCommand.test.ts +238 -0
  159. package/dist/ui/commands/setupGithubCommand.ts +212 -0
  160. package/dist/ui/commands/speakCommand.ts +175 -0
  161. package/dist/ui/commands/statsCommand.test.ts +78 -0
  162. package/dist/ui/commands/statsCommand.ts +70 -0
  163. package/dist/ui/commands/terminalSetupCommand.test.ts +85 -0
  164. package/dist/ui/commands/terminalSetupCommand.ts +45 -0
  165. package/dist/ui/commands/themeCommand.test.ts +38 -0
  166. package/dist/ui/commands/themeCommand.ts +17 -0
  167. package/dist/ui/commands/toolsCommand.test.ts +105 -0
  168. package/dist/ui/commands/toolsCommand.ts +71 -0
  169. package/dist/ui/commands/ttsCommand.ts +143 -0
  170. package/dist/ui/commands/types.ts +204 -0
  171. package/dist/ui/commands/vimCommand.ts +25 -0
  172. package/dist/ui/commands/voiceCommand.ts +125 -0
  173. package/dist/ui/components/AboutBox.tsx +133 -0
  174. package/dist/ui/components/AsciiArt.ts +54 -0
  175. package/dist/ui/components/AuthDialog.test.tsx +334 -0
  176. package/dist/ui/components/AuthDialog.tsx +289 -0
  177. package/dist/ui/components/AuthInProgress.tsx +62 -0
  178. package/dist/ui/components/AutoAcceptIndicator.tsx +47 -0
  179. package/dist/ui/components/ConsoleSummaryDisplay.tsx +35 -0
  180. package/dist/ui/components/ContextSummaryDisplay.test.tsx +85 -0
  181. package/dist/ui/components/ContextSummaryDisplay.tsx +120 -0
  182. package/dist/ui/components/ContextUsageDisplay.tsx +77 -0
  183. package/dist/ui/components/DebugProfiler.tsx +36 -0
  184. package/dist/ui/components/DetailedMessagesDisplay.tsx +82 -0
  185. package/dist/ui/components/EditorSettingsDialog.tsx +172 -0
  186. package/dist/ui/components/FolderTrustDialog.test.tsx +36 -0
  187. package/dist/ui/components/FolderTrustDialog.tsx +74 -0
  188. package/dist/ui/components/Footer.test.tsx +159 -0
  189. package/dist/ui/components/Footer.tsx +158 -0
  190. package/dist/ui/components/GeminiKeyDialog.tsx +252 -0
  191. package/dist/ui/components/GeminiRespondingSpinner.tsx +34 -0
  192. package/dist/ui/components/Header.test.tsx +44 -0
  193. package/dist/ui/components/Header.tsx +70 -0
  194. package/dist/ui/components/Help.tsx +174 -0
  195. package/dist/ui/components/HistoryItemDisplay.test.tsx +125 -0
  196. package/dist/ui/components/HistoryItemDisplay.tsx +98 -0
  197. package/dist/ui/components/InputPrompt.test.tsx +1467 -0
  198. package/dist/ui/components/InputPrompt.tsx +641 -0
  199. package/dist/ui/components/LMStudioModelPrompt.tsx +215 -0
  200. package/dist/ui/components/LoadingIndicator.test.tsx +296 -0
  201. package/dist/ui/components/LoadingIndicator.tsx +82 -0
  202. package/dist/ui/components/MemoryUsageDisplay.tsx +36 -0
  203. package/dist/ui/components/ModelStatsDisplay.test.tsx +252 -0
  204. package/dist/ui/components/ModelStatsDisplay.tsx +197 -0
  205. package/dist/ui/components/OllamaModelPrompt.tsx +206 -0
  206. package/dist/ui/components/OpenAIEndpointDialog.tsx +261 -0
  207. package/dist/ui/components/OpenAIKeyPrompt.test.tsx +64 -0
  208. package/dist/ui/components/OpenAIKeyPrompt.tsx +197 -0
  209. package/dist/ui/components/PrepareLabel.tsx +48 -0
  210. package/dist/ui/components/SearchEngineConfigDialog.tsx +280 -0
  211. package/dist/ui/components/SessionSummaryDisplay.test.tsx +75 -0
  212. package/dist/ui/components/SessionSummaryDisplay.tsx +18 -0
  213. package/dist/ui/components/SettingsDialog.test.tsx +865 -0
  214. package/dist/ui/components/SettingsDialog.tsx +753 -0
  215. package/dist/ui/components/ShellConfirmationDialog.test.tsx +53 -0
  216. package/dist/ui/components/ShellConfirmationDialog.tsx +103 -0
  217. package/dist/ui/components/ShellModeIndicator.tsx +18 -0
  218. package/dist/ui/components/ShowMoreLines.tsx +40 -0
  219. package/dist/ui/components/StatsDisplay.test.tsx +401 -0
  220. package/dist/ui/components/StatsDisplay.tsx +273 -0
  221. package/dist/ui/components/SuggestionsDisplay.tsx +102 -0
  222. package/dist/ui/components/ThemeDialog.tsx +310 -0
  223. package/dist/ui/components/Tips.tsx +45 -0
  224. package/dist/ui/components/TodoDisplay.test.tsx +97 -0
  225. package/dist/ui/components/TodoDisplay.tsx +72 -0
  226. package/dist/ui/components/ToolStatsDisplay.test.tsx +180 -0
  227. package/dist/ui/components/ToolStatsDisplay.tsx +208 -0
  228. package/dist/ui/components/UpdateNotification.tsx +23 -0
  229. package/dist/ui/components/WelcomeBackDialog.tsx +290 -0
  230. package/dist/ui/components/__snapshots__/IDEContextDetailDisplay.test.tsx.snap +24 -0
  231. package/dist/ui/components/__snapshots__/ModelStatsDisplay.test.tsx.snap +121 -0
  232. package/dist/ui/components/__snapshots__/SessionSummaryDisplay.test.tsx.snap +30 -0
  233. package/dist/ui/components/__snapshots__/ShellConfirmationDialog.test.tsx.snap +21 -0
  234. package/dist/ui/components/__snapshots__/StatsDisplay.test.tsx.snap +264 -0
  235. package/dist/ui/components/__snapshots__/ToolStatsDisplay.test.tsx.snap +91 -0
  236. package/dist/ui/components/messages/CompressionMessage.tsx +49 -0
  237. package/dist/ui/components/messages/DiffRenderer.test.tsx +365 -0
  238. package/dist/ui/components/messages/DiffRenderer.tsx +358 -0
  239. package/dist/ui/components/messages/ErrorMessage.tsx +31 -0
  240. package/dist/ui/components/messages/GeminiMessage.tsx +43 -0
  241. package/dist/ui/components/messages/GeminiMessageContent.tsx +43 -0
  242. package/dist/ui/components/messages/InfoMessage.tsx +32 -0
  243. package/dist/ui/components/messages/ToolConfirmationMessage.test.tsx +58 -0
  244. package/dist/ui/components/messages/ToolConfirmationMessage.tsx +297 -0
  245. package/dist/ui/components/messages/ToolGroupMessage.tsx +126 -0
  246. package/dist/ui/components/messages/ToolMessage.test.tsx +183 -0
  247. package/dist/ui/components/messages/ToolMessage.tsx +296 -0
  248. package/dist/ui/components/messages/UserMessage.tsx +43 -0
  249. package/dist/ui/components/messages/UserShellMessage.tsx +25 -0
  250. package/dist/ui/components/shared/MaxSizedBox.test.tsx +425 -0
  251. package/dist/ui/components/shared/MaxSizedBox.tsx +624 -0
  252. package/dist/ui/components/shared/RadioButtonSelect.test.tsx +181 -0
  253. package/dist/ui/components/shared/RadioButtonSelect.tsx +234 -0
  254. package/dist/ui/components/shared/__snapshots__/RadioButtonSelect.test.tsx.snap +47 -0
  255. package/dist/ui/components/shared/text-buffer.test.ts +1728 -0
  256. package/dist/ui/components/shared/text-buffer.ts +2227 -0
  257. package/dist/ui/components/shared/vim-buffer-actions.test.ts +1119 -0
  258. package/dist/ui/components/shared/vim-buffer-actions.ts +814 -0
  259. package/dist/ui/constants.ts +17 -0
  260. package/dist/ui/contexts/KeypressContext.test.tsx +391 -0
  261. package/dist/ui/contexts/KeypressContext.tsx +440 -0
  262. package/dist/ui/contexts/OverflowContext.tsx +87 -0
  263. package/dist/ui/contexts/SessionContext.test.tsx +132 -0
  264. package/dist/ui/contexts/SessionContext.tsx +143 -0
  265. package/dist/ui/contexts/SettingsContext.tsx +20 -0
  266. package/dist/ui/contexts/StreamingContext.tsx +22 -0
  267. package/dist/ui/contexts/VimModeContext.tsx +79 -0
  268. package/dist/ui/editors/editorSettingsManager.ts +66 -0
  269. package/dist/ui/hooks/atCommandProcessor.test.ts +1102 -0
  270. package/dist/ui/hooks/atCommandProcessor.ts +485 -0
  271. package/dist/ui/hooks/shellCommandProcessor.test.ts +481 -0
  272. package/dist/ui/hooks/shellCommandProcessor.ts +314 -0
  273. package/dist/ui/hooks/slashCommandProcessor.test.ts +1044 -0
  274. package/dist/ui/hooks/slashCommandProcessor.ts +595 -0
  275. package/dist/ui/hooks/useAtCompletion.test.ts +497 -0
  276. package/dist/ui/hooks/useAtCompletion.ts +244 -0
  277. package/dist/ui/hooks/useAuthCommand.ts +129 -0
  278. package/dist/ui/hooks/useAutoAcceptIndicator.test.ts +300 -0
  279. package/dist/ui/hooks/useAutoAcceptIndicator.ts +52 -0
  280. package/dist/ui/hooks/useBracketedPaste.ts +37 -0
  281. package/dist/ui/hooks/useCommandCompletion.test.ts +518 -0
  282. package/dist/ui/hooks/useCommandCompletion.tsx +238 -0
  283. package/dist/ui/hooks/useCompletion.ts +128 -0
  284. package/dist/ui/hooks/useConsoleMessages.test.ts +147 -0
  285. package/dist/ui/hooks/useConsoleMessages.ts +110 -0
  286. package/dist/ui/hooks/useEditorSettings.test.ts +283 -0
  287. package/dist/ui/hooks/useEditorSettings.ts +75 -0
  288. package/dist/ui/hooks/useFocus.test.ts +119 -0
  289. package/dist/ui/hooks/useFocus.ts +48 -0
  290. package/dist/ui/hooks/useFolderTrust.test.ts +159 -0
  291. package/dist/ui/hooks/useFolderTrust.ts +72 -0
  292. package/dist/ui/hooks/useGeminiStream.test.tsx +1998 -0
  293. package/dist/ui/hooks/useGeminiStream.ts +1017 -0
  294. package/dist/ui/hooks/useGitBranchName.test.ts +280 -0
  295. package/dist/ui/hooks/useGitBranchName.ts +79 -0
  296. package/dist/ui/hooks/useHistoryManager.test.ts +202 -0
  297. package/dist/ui/hooks/useHistoryManager.ts +111 -0
  298. package/dist/ui/hooks/useInputHistory.test.ts +261 -0
  299. package/dist/ui/hooks/useInputHistory.ts +111 -0
  300. package/dist/ui/hooks/useKeypress.test.ts +280 -0
  301. package/dist/ui/hooks/useKeypress.ts +39 -0
  302. package/dist/ui/hooks/useKittyKeyboardProtocol.ts +31 -0
  303. package/dist/ui/hooks/useLoadingIndicator.test.ts +139 -0
  304. package/dist/ui/hooks/useLoadingIndicator.ts +57 -0
  305. package/dist/ui/hooks/useLogger.ts +32 -0
  306. package/dist/ui/hooks/useMessageQueue.test.ts +226 -0
  307. package/dist/ui/hooks/useMessageQueue.ts +69 -0
  308. package/dist/ui/hooks/usePhraseCycler.test.ts +145 -0
  309. package/dist/ui/hooks/usePhraseCycler.ts +198 -0
  310. package/dist/ui/hooks/usePrivacySettings.test.ts +242 -0
  311. package/dist/ui/hooks/usePrivacySettings.ts +150 -0
  312. package/dist/ui/hooks/useReactToolScheduler.ts +309 -0
  313. package/dist/ui/hooks/useRefreshMemoryCommand.ts +7 -0
  314. package/dist/ui/hooks/useReverseSearchCompletion.test.tsx +260 -0
  315. package/dist/ui/hooks/useReverseSearchCompletion.tsx +95 -0
  316. package/dist/ui/hooks/useSettingsCommand.ts +25 -0
  317. package/dist/ui/hooks/useShellHistory.test.ts +219 -0
  318. package/dist/ui/hooks/useShellHistory.ts +133 -0
  319. package/dist/ui/hooks/useShowMemoryCommand.ts +75 -0
  320. package/dist/ui/hooks/useSlashCompletion.test.ts +434 -0
  321. package/dist/ui/hooks/useSlashCompletion.ts +187 -0
  322. package/dist/ui/hooks/useStateAndRef.ts +36 -0
  323. package/dist/ui/hooks/useTerminalSize.ts +32 -0
  324. package/dist/ui/hooks/useThemeCommand.ts +110 -0
  325. package/dist/ui/hooks/useTimer.test.ts +120 -0
  326. package/dist/ui/hooks/useTimer.ts +65 -0
  327. package/dist/ui/hooks/useToolScheduler.test.ts +1123 -0
  328. package/dist/ui/hooks/useWelcomeBack.ts +253 -0
  329. package/dist/ui/hooks/vim.test.ts +1691 -0
  330. package/dist/ui/hooks/vim.ts +784 -0
  331. package/dist/ui/keyMatchers.test.ts +337 -0
  332. package/dist/ui/keyMatchers.ts +105 -0
  333. package/dist/ui/privacy/CloudFreePrivacyNotice.tsx +117 -0
  334. package/dist/ui/privacy/CloudPaidPrivacyNotice.tsx +59 -0
  335. package/dist/ui/privacy/GeminiPrivacyNotice.tsx +62 -0
  336. package/dist/ui/privacy/PrivacyNotice.tsx +42 -0
  337. package/dist/ui/semantic-colors.ts +26 -0
  338. package/dist/ui/themes/ansi-light.ts +150 -0
  339. package/dist/ui/themes/ansi.ts +159 -0
  340. package/dist/ui/themes/atom-one-dark.ts +147 -0
  341. package/dist/ui/themes/ayu-light.ts +139 -0
  342. package/dist/ui/themes/ayu.ts +113 -0
  343. package/dist/ui/themes/color-utils.test.ts +221 -0
  344. package/dist/ui/themes/color-utils.ts +231 -0
  345. package/dist/ui/themes/default-light.ts +108 -0
  346. package/dist/ui/themes/default.ts +151 -0
  347. package/dist/ui/themes/dracula.ts +124 -0
  348. package/dist/ui/themes/fss-code-dark.ts +156 -0
  349. package/dist/ui/themes/fss-dark.ts +113 -0
  350. package/dist/ui/themes/fss-light.ts +139 -0
  351. package/dist/ui/themes/github-dark.ts +147 -0
  352. package/dist/ui/themes/github-light.ts +149 -0
  353. package/dist/ui/themes/googlecode.ts +146 -0
  354. package/dist/ui/themes/no-color.ts +125 -0
  355. package/dist/ui/themes/qwen-dark.ts +118 -0
  356. package/dist/ui/themes/qwen-light.ts +144 -0
  357. package/dist/ui/themes/semantic-tokens.ts +127 -0
  358. package/dist/ui/themes/shades-of-purple.ts +352 -0
  359. package/dist/ui/themes/theme-manager.test.ts +99 -0
  360. package/dist/ui/themes/theme-manager.ts +257 -0
  361. package/dist/ui/themes/theme.test.ts +97 -0
  362. package/dist/ui/themes/theme.ts +451 -0
  363. package/dist/ui/themes/xcode.ts +154 -0
  364. package/dist/ui/types.ts +255 -0
  365. package/dist/ui/utils/CodeColorizer.tsx +217 -0
  366. package/dist/ui/utils/ConsolePatcher.ts +71 -0
  367. package/dist/ui/utils/InlineMarkdownRenderer.tsx +173 -0
  368. package/dist/ui/utils/MarkdownDisplay.test.tsx +244 -0
  369. package/dist/ui/utils/MarkdownDisplay.tsx +415 -0
  370. package/dist/ui/utils/TableRenderer.tsx +159 -0
  371. package/dist/ui/utils/__snapshots__/MarkdownDisplay.test.tsx.snap +93 -0
  372. package/dist/ui/utils/clipboardUtils.test.ts +76 -0
  373. package/dist/ui/utils/clipboardUtils.ts +149 -0
  374. package/dist/ui/utils/commandUtils.test.ts +384 -0
  375. package/dist/ui/utils/commandUtils.ts +106 -0
  376. package/dist/ui/utils/computeStats.test.ts +292 -0
  377. package/dist/ui/utils/computeStats.ts +86 -0
  378. package/dist/ui/utils/displayUtils.test.ts +58 -0
  379. package/dist/ui/utils/displayUtils.ts +32 -0
  380. package/dist/ui/utils/formatters.test.ts +72 -0
  381. package/dist/ui/utils/formatters.ts +63 -0
  382. package/dist/ui/utils/isNarrowWidth.ts +9 -0
  383. package/dist/ui/utils/kittyProtocolDetector.ts +105 -0
  384. package/dist/ui/utils/markdownUtilities.test.ts +50 -0
  385. package/dist/ui/utils/markdownUtilities.ts +125 -0
  386. package/dist/ui/utils/platformConstants.ts +52 -0
  387. package/dist/ui/utils/terminalSetup.ts +342 -0
  388. package/dist/ui/utils/textUtils.ts +40 -0
  389. package/dist/ui/utils/updateCheck.test.ts +163 -0
  390. package/dist/ui/utils/updateCheck.ts +100 -0
  391. package/dist/utils/checks.ts +28 -0
  392. package/dist/utils/cleanup.test.ts +68 -0
  393. package/dist/utils/cleanup.ts +36 -0
  394. package/dist/utils/dialogScopeUtils.ts +64 -0
  395. package/dist/utils/events.ts +14 -0
  396. package/dist/utils/gitUtils.test.ts +149 -0
  397. package/dist/utils/gitUtils.ts +116 -0
  398. package/dist/utils/handleAutoUpdate.test.ts +272 -0
  399. package/dist/utils/handleAutoUpdate.ts +145 -0
  400. package/dist/utils/installationInfo.test.ts +315 -0
  401. package/dist/utils/installationInfo.ts +176 -0
  402. package/dist/utils/package.ts +38 -0
  403. package/dist/utils/readStdin.ts +51 -0
  404. package/dist/utils/resolvePath.ts +21 -0
  405. package/dist/utils/sandbox-macos-permissive-closed.sb +32 -0
  406. package/dist/utils/sandbox-macos-permissive-open.sb +25 -0
  407. package/dist/utils/sandbox-macos-permissive-proxied.sb +37 -0
  408. package/dist/utils/sandbox-macos-restrictive-closed.sb +93 -0
  409. package/dist/utils/sandbox-macos-restrictive-open.sb +96 -0
  410. package/dist/utils/sandbox-macos-restrictive-proxied.sb +98 -0
  411. package/dist/utils/sandbox.ts +962 -0
  412. package/dist/utils/settingsUtils.test.ts +797 -0
  413. package/dist/utils/settingsUtils.ts +489 -0
  414. package/dist/utils/spawnWrapper.ts +9 -0
  415. package/dist/utils/startupWarnings.test.ts +83 -0
  416. package/dist/utils/startupWarnings.ts +40 -0
  417. package/dist/utils/updateEventEmitter.ts +13 -0
  418. package/dist/utils/userStartupWarnings.test.ts +87 -0
  419. package/dist/utils/userStartupWarnings.ts +69 -0
  420. package/dist/utils/version.ts +12 -0
  421. package/dist/validateNonInterActiveAuth.test.ts +260 -0
  422. package/dist/validateNonInterActiveAuth.ts +51 -0
  423. package/dist/vitest.config.ts +37 -0
  424. package/dist/zed-integration/acp.ts +366 -0
  425. package/dist/zed-integration/fileSystemService.ts +47 -0
  426. package/dist/zed-integration/schema.ts +466 -0
  427. package/dist/zed-integration/zedIntegration.ts +944 -0
  428. package/package.json +2 -2
@@ -0,0 +1,814 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import {
8
+ TextBufferState,
9
+ TextBufferAction,
10
+ getLineRangeOffsets,
11
+ getPositionFromOffsets,
12
+ replaceRangeInternal,
13
+ pushUndo,
14
+ isWordCharStrict,
15
+ isWordCharWithCombining,
16
+ isCombiningMark,
17
+ findNextWordAcrossLines,
18
+ findPrevWordAcrossLines,
19
+ findWordEndInLine,
20
+ } from './text-buffer.js';
21
+ import { cpLen, toCodePoints } from '../../utils/textUtils.js';
22
+ import { assumeExhaustive } from '../../../utils/checks.js';
23
+
24
+ // Check if we're at the end of a base word (on the last base character)
25
+ // Returns true if current position has a base character followed only by combining marks until non-word
26
+ function isAtEndOfBaseWord(lineCodePoints: string[], col: number): boolean {
27
+ if (!isWordCharStrict(lineCodePoints[col])) return false;
28
+
29
+ // Look ahead to see if we have only combining marks followed by non-word
30
+ let i = col + 1;
31
+
32
+ // Skip any combining marks
33
+ while (i < lineCodePoints.length && isCombiningMark(lineCodePoints[i])) {
34
+ i++;
35
+ }
36
+
37
+ // If we hit end of line or non-word character, we were at end of base word
38
+ return i >= lineCodePoints.length || !isWordCharStrict(lineCodePoints[i]);
39
+ }
40
+
41
+ export type VimAction = Extract<
42
+ TextBufferAction,
43
+ | { type: 'vim_delete_word_forward' }
44
+ | { type: 'vim_delete_word_backward' }
45
+ | { type: 'vim_delete_word_end' }
46
+ | { type: 'vim_change_word_forward' }
47
+ | { type: 'vim_change_word_backward' }
48
+ | { type: 'vim_change_word_end' }
49
+ | { type: 'vim_delete_line' }
50
+ | { type: 'vim_change_line' }
51
+ | { type: 'vim_delete_to_end_of_line' }
52
+ | { type: 'vim_change_to_end_of_line' }
53
+ | { type: 'vim_change_movement' }
54
+ | { type: 'vim_move_left' }
55
+ | { type: 'vim_move_right' }
56
+ | { type: 'vim_move_up' }
57
+ | { type: 'vim_move_down' }
58
+ | { type: 'vim_move_word_forward' }
59
+ | { type: 'vim_move_word_backward' }
60
+ | { type: 'vim_move_word_end' }
61
+ | { type: 'vim_delete_char' }
62
+ | { type: 'vim_insert_at_cursor' }
63
+ | { type: 'vim_append_at_cursor' }
64
+ | { type: 'vim_open_line_below' }
65
+ | { type: 'vim_open_line_above' }
66
+ | { type: 'vim_append_at_line_end' }
67
+ | { type: 'vim_insert_at_line_start' }
68
+ | { type: 'vim_move_to_line_start' }
69
+ | { type: 'vim_move_to_line_end' }
70
+ | { type: 'vim_move_to_first_nonwhitespace' }
71
+ | { type: 'vim_move_to_first_line' }
72
+ | { type: 'vim_move_to_last_line' }
73
+ | { type: 'vim_move_to_line' }
74
+ | { type: 'vim_escape_insert_mode' }
75
+ >;
76
+
77
+ export function handleVimAction(
78
+ state: TextBufferState,
79
+ action: VimAction,
80
+ ): TextBufferState {
81
+ const { lines, cursorRow, cursorCol } = state;
82
+
83
+ switch (action.type) {
84
+ case 'vim_delete_word_forward':
85
+ case 'vim_change_word_forward': {
86
+ const { count } = action.payload;
87
+ let endRow = cursorRow;
88
+ let endCol = cursorCol;
89
+
90
+ for (let i = 0; i < count; i++) {
91
+ const nextWord = findNextWordAcrossLines(lines, endRow, endCol, true);
92
+ if (nextWord) {
93
+ endRow = nextWord.row;
94
+ endCol = nextWord.col;
95
+ } else {
96
+ // No more words, delete/change to end of current word or line
97
+ const currentLine = lines[endRow] || '';
98
+ const wordEnd = findWordEndInLine(currentLine, endCol);
99
+ if (wordEnd !== null) {
100
+ endCol = wordEnd + 1; // Include the character at word end
101
+ } else {
102
+ endCol = cpLen(currentLine);
103
+ }
104
+ break;
105
+ }
106
+ }
107
+
108
+ if (endRow !== cursorRow || endCol !== cursorCol) {
109
+ const nextState = pushUndo(state);
110
+ return replaceRangeInternal(
111
+ nextState,
112
+ cursorRow,
113
+ cursorCol,
114
+ endRow,
115
+ endCol,
116
+ '',
117
+ );
118
+ }
119
+ return state;
120
+ }
121
+
122
+ case 'vim_delete_word_backward':
123
+ case 'vim_change_word_backward': {
124
+ const { count } = action.payload;
125
+ let startRow = cursorRow;
126
+ let startCol = cursorCol;
127
+
128
+ for (let i = 0; i < count; i++) {
129
+ const prevWord = findPrevWordAcrossLines(lines, startRow, startCol);
130
+ if (prevWord) {
131
+ startRow = prevWord.row;
132
+ startCol = prevWord.col;
133
+ } else {
134
+ break;
135
+ }
136
+ }
137
+
138
+ if (startRow !== cursorRow || startCol !== cursorCol) {
139
+ const nextState = pushUndo(state);
140
+ return replaceRangeInternal(
141
+ nextState,
142
+ startRow,
143
+ startCol,
144
+ cursorRow,
145
+ cursorCol,
146
+ '',
147
+ );
148
+ }
149
+ return state;
150
+ }
151
+
152
+ case 'vim_delete_word_end':
153
+ case 'vim_change_word_end': {
154
+ const { count } = action.payload;
155
+ let row = cursorRow;
156
+ let col = cursorCol;
157
+ let endRow = cursorRow;
158
+ let endCol = cursorCol;
159
+
160
+ for (let i = 0; i < count; i++) {
161
+ const wordEnd = findNextWordAcrossLines(lines, row, col, false);
162
+ if (wordEnd) {
163
+ endRow = wordEnd.row;
164
+ endCol = wordEnd.col + 1; // Include the character at word end
165
+ // For next iteration, move to start of next word
166
+ if (i < count - 1) {
167
+ const nextWord = findNextWordAcrossLines(
168
+ lines,
169
+ wordEnd.row,
170
+ wordEnd.col + 1,
171
+ true,
172
+ );
173
+ if (nextWord) {
174
+ row = nextWord.row;
175
+ col = nextWord.col;
176
+ } else {
177
+ break; // No more words
178
+ }
179
+ }
180
+ } else {
181
+ break;
182
+ }
183
+ }
184
+
185
+ // Ensure we don't go past the end of the last line
186
+ if (endRow < lines.length) {
187
+ const lineLen = cpLen(lines[endRow] || '');
188
+ endCol = Math.min(endCol, lineLen);
189
+ }
190
+
191
+ if (endRow !== cursorRow || endCol !== cursorCol) {
192
+ const nextState = pushUndo(state);
193
+ return replaceRangeInternal(
194
+ nextState,
195
+ cursorRow,
196
+ cursorCol,
197
+ endRow,
198
+ endCol,
199
+ '',
200
+ );
201
+ }
202
+ return state;
203
+ }
204
+
205
+ case 'vim_delete_line': {
206
+ const { count } = action.payload;
207
+ if (lines.length === 0) return state;
208
+
209
+ const linesToDelete = Math.min(count, lines.length - cursorRow);
210
+ const totalLines = lines.length;
211
+
212
+ if (totalLines === 1 || linesToDelete >= totalLines) {
213
+ // If there's only one line, or we're deleting all remaining lines,
214
+ // clear the content but keep one empty line (text editors should never be completely empty)
215
+ const nextState = pushUndo(state);
216
+ return {
217
+ ...nextState,
218
+ lines: [''],
219
+ cursorRow: 0,
220
+ cursorCol: 0,
221
+ preferredCol: null,
222
+ };
223
+ }
224
+
225
+ const nextState = pushUndo(state);
226
+ const newLines = [...nextState.lines];
227
+ newLines.splice(cursorRow, linesToDelete);
228
+
229
+ // Adjust cursor position
230
+ const newCursorRow = Math.min(cursorRow, newLines.length - 1);
231
+ const newCursorCol = 0; // Vim places cursor at beginning of line after dd
232
+
233
+ return {
234
+ ...nextState,
235
+ lines: newLines,
236
+ cursorRow: newCursorRow,
237
+ cursorCol: newCursorCol,
238
+ preferredCol: null,
239
+ };
240
+ }
241
+
242
+ case 'vim_change_line': {
243
+ const { count } = action.payload;
244
+ if (lines.length === 0) return state;
245
+
246
+ const linesToChange = Math.min(count, lines.length - cursorRow);
247
+ const nextState = pushUndo(state);
248
+
249
+ const { startOffset, endOffset } = getLineRangeOffsets(
250
+ cursorRow,
251
+ linesToChange,
252
+ nextState.lines,
253
+ );
254
+ const { startRow, startCol, endRow, endCol } = getPositionFromOffsets(
255
+ startOffset,
256
+ endOffset,
257
+ nextState.lines,
258
+ );
259
+ return replaceRangeInternal(
260
+ nextState,
261
+ startRow,
262
+ startCol,
263
+ endRow,
264
+ endCol,
265
+ '',
266
+ );
267
+ }
268
+
269
+ case 'vim_delete_to_end_of_line':
270
+ case 'vim_change_to_end_of_line': {
271
+ const currentLine = lines[cursorRow] || '';
272
+ if (cursorCol < cpLen(currentLine)) {
273
+ const nextState = pushUndo(state);
274
+ return replaceRangeInternal(
275
+ nextState,
276
+ cursorRow,
277
+ cursorCol,
278
+ cursorRow,
279
+ cpLen(currentLine),
280
+ '',
281
+ );
282
+ }
283
+ return state;
284
+ }
285
+
286
+ case 'vim_change_movement': {
287
+ const { movement, count } = action.payload;
288
+ const totalLines = lines.length;
289
+
290
+ switch (movement) {
291
+ case 'h': {
292
+ // Left
293
+ // Change N characters to the left
294
+ const startCol = Math.max(0, cursorCol - count);
295
+ return replaceRangeInternal(
296
+ pushUndo(state),
297
+ cursorRow,
298
+ startCol,
299
+ cursorRow,
300
+ cursorCol,
301
+ '',
302
+ );
303
+ }
304
+
305
+ case 'j': {
306
+ // Down
307
+ const linesToChange = Math.min(count, totalLines - cursorRow);
308
+ if (linesToChange > 0) {
309
+ if (totalLines === 1) {
310
+ const currentLine = state.lines[0] || '';
311
+ return replaceRangeInternal(
312
+ pushUndo(state),
313
+ 0,
314
+ 0,
315
+ 0,
316
+ cpLen(currentLine),
317
+ '',
318
+ );
319
+ } else {
320
+ const nextState = pushUndo(state);
321
+ const { startOffset, endOffset } = getLineRangeOffsets(
322
+ cursorRow,
323
+ linesToChange,
324
+ nextState.lines,
325
+ );
326
+ const { startRow, startCol, endRow, endCol } =
327
+ getPositionFromOffsets(startOffset, endOffset, nextState.lines);
328
+ return replaceRangeInternal(
329
+ nextState,
330
+ startRow,
331
+ startCol,
332
+ endRow,
333
+ endCol,
334
+ '',
335
+ );
336
+ }
337
+ }
338
+ return state;
339
+ }
340
+
341
+ case 'k': {
342
+ // Up
343
+ const upLines = Math.min(count, cursorRow + 1);
344
+ if (upLines > 0) {
345
+ if (state.lines.length === 1) {
346
+ const currentLine = state.lines[0] || '';
347
+ return replaceRangeInternal(
348
+ pushUndo(state),
349
+ 0,
350
+ 0,
351
+ 0,
352
+ cpLen(currentLine),
353
+ '',
354
+ );
355
+ } else {
356
+ const startRow = Math.max(0, cursorRow - count + 1);
357
+ const linesToChange = cursorRow - startRow + 1;
358
+ const nextState = pushUndo(state);
359
+ const { startOffset, endOffset } = getLineRangeOffsets(
360
+ startRow,
361
+ linesToChange,
362
+ nextState.lines,
363
+ );
364
+ const {
365
+ startRow: newStartRow,
366
+ startCol,
367
+ endRow,
368
+ endCol,
369
+ } = getPositionFromOffsets(
370
+ startOffset,
371
+ endOffset,
372
+ nextState.lines,
373
+ );
374
+ const resultState = replaceRangeInternal(
375
+ nextState,
376
+ newStartRow,
377
+ startCol,
378
+ endRow,
379
+ endCol,
380
+ '',
381
+ );
382
+ return {
383
+ ...resultState,
384
+ cursorRow: startRow,
385
+ cursorCol: 0,
386
+ };
387
+ }
388
+ }
389
+ return state;
390
+ }
391
+
392
+ case 'l': {
393
+ // Right
394
+ // Change N characters to the right
395
+ return replaceRangeInternal(
396
+ pushUndo(state),
397
+ cursorRow,
398
+ cursorCol,
399
+ cursorRow,
400
+ Math.min(cpLen(lines[cursorRow] || ''), cursorCol + count),
401
+ '',
402
+ );
403
+ }
404
+
405
+ default:
406
+ return state;
407
+ }
408
+ }
409
+
410
+ case 'vim_move_left': {
411
+ const { count } = action.payload;
412
+ const { cursorRow, cursorCol, lines } = state;
413
+ let newRow = cursorRow;
414
+ let newCol = cursorCol;
415
+
416
+ for (let i = 0; i < count; i++) {
417
+ if (newCol > 0) {
418
+ newCol--;
419
+ } else if (newRow > 0) {
420
+ // Move to end of previous line
421
+ newRow--;
422
+ const prevLine = lines[newRow] || '';
423
+ const prevLineLength = cpLen(prevLine);
424
+ // Position on last character, or column 0 for empty lines
425
+ newCol = prevLineLength === 0 ? 0 : prevLineLength - 1;
426
+ }
427
+ }
428
+
429
+ return {
430
+ ...state,
431
+ cursorRow: newRow,
432
+ cursorCol: newCol,
433
+ preferredCol: null,
434
+ };
435
+ }
436
+
437
+ case 'vim_move_right': {
438
+ const { count } = action.payload;
439
+ const { cursorRow, cursorCol, lines } = state;
440
+ let newRow = cursorRow;
441
+ let newCol = cursorCol;
442
+
443
+ for (let i = 0; i < count; i++) {
444
+ const currentLine = lines[newRow] || '';
445
+ const lineLength = cpLen(currentLine);
446
+ // Don't move past the last character of the line
447
+ // For empty lines, stay at column 0; for non-empty lines, don't go past last character
448
+ if (lineLength === 0) {
449
+ // Empty line - try to move to next line
450
+ if (newRow < lines.length - 1) {
451
+ newRow++;
452
+ newCol = 0;
453
+ }
454
+ } else if (newCol < lineLength - 1) {
455
+ newCol++;
456
+
457
+ // Skip over combining marks - don't let cursor land on them
458
+ const currentLinePoints = toCodePoints(currentLine);
459
+ while (
460
+ newCol < currentLinePoints.length &&
461
+ isCombiningMark(currentLinePoints[newCol]) &&
462
+ newCol < lineLength - 1
463
+ ) {
464
+ newCol++;
465
+ }
466
+ } else if (newRow < lines.length - 1) {
467
+ // At end of line - move to beginning of next line
468
+ newRow++;
469
+ newCol = 0;
470
+ }
471
+ }
472
+
473
+ return {
474
+ ...state,
475
+ cursorRow: newRow,
476
+ cursorCol: newCol,
477
+ preferredCol: null,
478
+ };
479
+ }
480
+
481
+ case 'vim_move_up': {
482
+ const { count } = action.payload;
483
+ const { cursorRow, cursorCol, lines } = state;
484
+ const newRow = Math.max(0, cursorRow - count);
485
+ const targetLine = lines[newRow] || '';
486
+ const targetLineLength = cpLen(targetLine);
487
+ const newCol = Math.min(
488
+ cursorCol,
489
+ targetLineLength > 0 ? targetLineLength - 1 : 0,
490
+ );
491
+
492
+ return {
493
+ ...state,
494
+ cursorRow: newRow,
495
+ cursorCol: newCol,
496
+ preferredCol: null,
497
+ };
498
+ }
499
+
500
+ case 'vim_move_down': {
501
+ const { count } = action.payload;
502
+ const { cursorRow, cursorCol, lines } = state;
503
+ const newRow = Math.min(lines.length - 1, cursorRow + count);
504
+ const targetLine = lines[newRow] || '';
505
+ const targetLineLength = cpLen(targetLine);
506
+ const newCol = Math.min(
507
+ cursorCol,
508
+ targetLineLength > 0 ? targetLineLength - 1 : 0,
509
+ );
510
+
511
+ return {
512
+ ...state,
513
+ cursorRow: newRow,
514
+ cursorCol: newCol,
515
+ preferredCol: null,
516
+ };
517
+ }
518
+
519
+ case 'vim_move_word_forward': {
520
+ const { count } = action.payload;
521
+ let row = cursorRow;
522
+ let col = cursorCol;
523
+
524
+ for (let i = 0; i < count; i++) {
525
+ const nextWord = findNextWordAcrossLines(lines, row, col, true);
526
+ if (nextWord) {
527
+ row = nextWord.row;
528
+ col = nextWord.col;
529
+ } else {
530
+ // No more words to move to
531
+ break;
532
+ }
533
+ }
534
+
535
+ return {
536
+ ...state,
537
+ cursorRow: row,
538
+ cursorCol: col,
539
+ preferredCol: null,
540
+ };
541
+ }
542
+
543
+ case 'vim_move_word_backward': {
544
+ const { count } = action.payload;
545
+ let row = cursorRow;
546
+ let col = cursorCol;
547
+
548
+ for (let i = 0; i < count; i++) {
549
+ const prevWord = findPrevWordAcrossLines(lines, row, col);
550
+ if (prevWord) {
551
+ row = prevWord.row;
552
+ col = prevWord.col;
553
+ } else {
554
+ break;
555
+ }
556
+ }
557
+
558
+ return {
559
+ ...state,
560
+ cursorRow: row,
561
+ cursorCol: col,
562
+ preferredCol: null,
563
+ };
564
+ }
565
+
566
+ case 'vim_move_word_end': {
567
+ const { count } = action.payload;
568
+ let row = cursorRow;
569
+ let col = cursorCol;
570
+
571
+ for (let i = 0; i < count; i++) {
572
+ // Special handling for the first iteration when we're at end of word
573
+ if (i === 0) {
574
+ const currentLine = lines[row] || '';
575
+ const lineCodePoints = toCodePoints(currentLine);
576
+
577
+ // Check if we're at the end of a word (on the last base character)
578
+ const atEndOfWord =
579
+ col < lineCodePoints.length &&
580
+ isWordCharStrict(lineCodePoints[col]) &&
581
+ (col + 1 >= lineCodePoints.length ||
582
+ !isWordCharWithCombining(lineCodePoints[col + 1]) ||
583
+ // Or if we're on a base char followed only by combining marks until non-word
584
+ (isWordCharStrict(lineCodePoints[col]) &&
585
+ isAtEndOfBaseWord(lineCodePoints, col)));
586
+
587
+ if (atEndOfWord) {
588
+ // We're already at end of word, find next word end
589
+ const nextWord = findNextWordAcrossLines(
590
+ lines,
591
+ row,
592
+ col + 1,
593
+ false,
594
+ );
595
+ if (nextWord) {
596
+ row = nextWord.row;
597
+ col = nextWord.col;
598
+ continue;
599
+ }
600
+ }
601
+ }
602
+
603
+ const wordEnd = findNextWordAcrossLines(lines, row, col, false);
604
+ if (wordEnd) {
605
+ row = wordEnd.row;
606
+ col = wordEnd.col;
607
+ } else {
608
+ break;
609
+ }
610
+ }
611
+
612
+ return {
613
+ ...state,
614
+ cursorRow: row,
615
+ cursorCol: col,
616
+ preferredCol: null,
617
+ };
618
+ }
619
+
620
+ case 'vim_delete_char': {
621
+ const { count } = action.payload;
622
+ const { cursorRow, cursorCol, lines } = state;
623
+ const currentLine = lines[cursorRow] || '';
624
+ const lineLength = cpLen(currentLine);
625
+
626
+ if (cursorCol < lineLength) {
627
+ const deleteCount = Math.min(count, lineLength - cursorCol);
628
+ const nextState = pushUndo(state);
629
+ return replaceRangeInternal(
630
+ nextState,
631
+ cursorRow,
632
+ cursorCol,
633
+ cursorRow,
634
+ cursorCol + deleteCount,
635
+ '',
636
+ );
637
+ }
638
+ return state;
639
+ }
640
+
641
+ case 'vim_insert_at_cursor': {
642
+ // Just return state - mode change is handled elsewhere
643
+ return state;
644
+ }
645
+
646
+ case 'vim_append_at_cursor': {
647
+ const { cursorRow, cursorCol, lines } = state;
648
+ const currentLine = lines[cursorRow] || '';
649
+ const newCol = cursorCol < cpLen(currentLine) ? cursorCol + 1 : cursorCol;
650
+
651
+ return {
652
+ ...state,
653
+ cursorCol: newCol,
654
+ preferredCol: null,
655
+ };
656
+ }
657
+
658
+ case 'vim_open_line_below': {
659
+ const { cursorRow, lines } = state;
660
+ const nextState = pushUndo(state);
661
+
662
+ // Insert newline at end of current line
663
+ const endOfLine = cpLen(lines[cursorRow] || '');
664
+ return replaceRangeInternal(
665
+ nextState,
666
+ cursorRow,
667
+ endOfLine,
668
+ cursorRow,
669
+ endOfLine,
670
+ '\n',
671
+ );
672
+ }
673
+
674
+ case 'vim_open_line_above': {
675
+ const { cursorRow } = state;
676
+ const nextState = pushUndo(state);
677
+
678
+ // Insert newline at beginning of current line
679
+ const resultState = replaceRangeInternal(
680
+ nextState,
681
+ cursorRow,
682
+ 0,
683
+ cursorRow,
684
+ 0,
685
+ '\n',
686
+ );
687
+
688
+ // Move cursor to the new line above
689
+ return {
690
+ ...resultState,
691
+ cursorRow,
692
+ cursorCol: 0,
693
+ };
694
+ }
695
+
696
+ case 'vim_append_at_line_end': {
697
+ const { cursorRow, lines } = state;
698
+ const lineLength = cpLen(lines[cursorRow] || '');
699
+
700
+ return {
701
+ ...state,
702
+ cursorCol: lineLength,
703
+ preferredCol: null,
704
+ };
705
+ }
706
+
707
+ case 'vim_insert_at_line_start': {
708
+ const { cursorRow, lines } = state;
709
+ const currentLine = lines[cursorRow] || '';
710
+ let col = 0;
711
+
712
+ // Find first non-whitespace character using proper Unicode handling
713
+ const lineCodePoints = toCodePoints(currentLine);
714
+ while (col < lineCodePoints.length && /\s/.test(lineCodePoints[col])) {
715
+ col++;
716
+ }
717
+
718
+ return {
719
+ ...state,
720
+ cursorCol: col,
721
+ preferredCol: null,
722
+ };
723
+ }
724
+
725
+ case 'vim_move_to_line_start': {
726
+ return {
727
+ ...state,
728
+ cursorCol: 0,
729
+ preferredCol: null,
730
+ };
731
+ }
732
+
733
+ case 'vim_move_to_line_end': {
734
+ const { cursorRow, lines } = state;
735
+ const lineLength = cpLen(lines[cursorRow] || '');
736
+
737
+ return {
738
+ ...state,
739
+ cursorCol: lineLength > 0 ? lineLength - 1 : 0,
740
+ preferredCol: null,
741
+ };
742
+ }
743
+
744
+ case 'vim_move_to_first_nonwhitespace': {
745
+ const { cursorRow, lines } = state;
746
+ const currentLine = lines[cursorRow] || '';
747
+ let col = 0;
748
+
749
+ // Find first non-whitespace character using proper Unicode handling
750
+ const lineCodePoints = toCodePoints(currentLine);
751
+ while (col < lineCodePoints.length && /\s/.test(lineCodePoints[col])) {
752
+ col++;
753
+ }
754
+
755
+ return {
756
+ ...state,
757
+ cursorCol: col,
758
+ preferredCol: null,
759
+ };
760
+ }
761
+
762
+ case 'vim_move_to_first_line': {
763
+ return {
764
+ ...state,
765
+ cursorRow: 0,
766
+ cursorCol: 0,
767
+ preferredCol: null,
768
+ };
769
+ }
770
+
771
+ case 'vim_move_to_last_line': {
772
+ const { lines } = state;
773
+ const lastRow = lines.length - 1;
774
+
775
+ return {
776
+ ...state,
777
+ cursorRow: lastRow,
778
+ cursorCol: 0,
779
+ preferredCol: null,
780
+ };
781
+ }
782
+
783
+ case 'vim_move_to_line': {
784
+ const { lineNumber } = action.payload;
785
+ const { lines } = state;
786
+ const targetRow = Math.min(Math.max(0, lineNumber - 1), lines.length - 1);
787
+
788
+ return {
789
+ ...state,
790
+ cursorRow: targetRow,
791
+ cursorCol: 0,
792
+ preferredCol: null,
793
+ };
794
+ }
795
+
796
+ case 'vim_escape_insert_mode': {
797
+ // Move cursor left if not at beginning of line (vim behavior when exiting insert mode)
798
+ const { cursorCol } = state;
799
+ const newCol = cursorCol > 0 ? cursorCol - 1 : 0;
800
+
801
+ return {
802
+ ...state,
803
+ cursorCol: newCol,
804
+ preferredCol: null,
805
+ };
806
+ }
807
+
808
+ default: {
809
+ // This should never happen if TypeScript is working correctly
810
+ assumeExhaustive(action);
811
+ return state;
812
+ }
813
+ }
814
+ }