@travisennis/acai 0.0.5 → 0.0.7

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 (421) hide show
  1. package/README.md +190 -19
  2. package/bin/acai-wrapper.js +26 -0
  3. package/dist/agent/index.d.ts +132 -0
  4. package/dist/agent/index.d.ts.map +1 -0
  5. package/dist/agent/index.js +434 -0
  6. package/dist/api/exa/index.js +1 -1
  7. package/dist/cli.d.ts +4 -1
  8. package/dist/cli.d.ts.map +1 -1
  9. package/dist/cli.js +67 -40
  10. package/dist/commands/add-directory-command.d.ts +3 -0
  11. package/dist/commands/add-directory-command.d.ts.map +1 -0
  12. package/dist/commands/add-directory-command.js +54 -0
  13. package/dist/commands/application-log-command.d.ts +1 -1
  14. package/dist/commands/application-log-command.d.ts.map +1 -1
  15. package/dist/commands/application-log-command.js +18 -20
  16. package/dist/commands/clear-command.d.ts +1 -1
  17. package/dist/commands/clear-command.d.ts.map +1 -1
  18. package/dist/commands/clear-command.js +7 -3
  19. package/dist/commands/compact-command.d.ts.map +1 -1
  20. package/dist/commands/compact-command.js +9 -5
  21. package/dist/commands/context-command.d.ts +3 -0
  22. package/dist/commands/context-command.d.ts.map +1 -0
  23. package/dist/commands/context-command.js +124 -0
  24. package/dist/commands/copy-command.d.ts.map +1 -1
  25. package/dist/commands/copy-command.js +14 -5
  26. package/dist/commands/edit-command.d.ts +1 -1
  27. package/dist/commands/edit-command.d.ts.map +1 -1
  28. package/dist/commands/edit-command.js +21 -34
  29. package/dist/commands/edit-prompt-command.d.ts +1 -1
  30. package/dist/commands/edit-prompt-command.d.ts.map +1 -1
  31. package/dist/commands/edit-prompt-command.js +18 -15
  32. package/dist/commands/exit-command.d.ts +1 -4
  33. package/dist/commands/exit-command.d.ts.map +1 -1
  34. package/dist/commands/exit-command.js +9 -5
  35. package/dist/commands/files-command.d.ts +1 -1
  36. package/dist/commands/files-command.d.ts.map +1 -1
  37. package/dist/commands/files-command.js +20 -16
  38. package/dist/commands/generate-rules-command.d.ts +1 -1
  39. package/dist/commands/generate-rules-command.d.ts.map +1 -1
  40. package/dist/commands/generate-rules-command.js +307 -39
  41. package/dist/commands/handoff-command.d.ts +3 -0
  42. package/dist/commands/handoff-command.d.ts.map +1 -0
  43. package/dist/commands/handoff-command.js +191 -0
  44. package/dist/commands/health-command.d.ts +1 -1
  45. package/dist/commands/health-command.d.ts.map +1 -1
  46. package/dist/commands/health-command.js +49 -27
  47. package/dist/commands/help-command.d.ts +1 -1
  48. package/dist/commands/help-command.d.ts.map +1 -1
  49. package/dist/commands/help-command.js +25 -5
  50. package/dist/commands/history-command.d.ts +3 -0
  51. package/dist/commands/history-command.d.ts.map +1 -0
  52. package/dist/commands/history-command.js +458 -0
  53. package/dist/commands/init-command.d.ts +1 -1
  54. package/dist/commands/init-command.d.ts.map +1 -1
  55. package/dist/commands/init-command.js +40 -22
  56. package/dist/commands/last-log-command.d.ts +1 -1
  57. package/dist/commands/last-log-command.d.ts.map +1 -1
  58. package/dist/commands/last-log-command.js +15 -15
  59. package/dist/commands/list-directories-command.d.ts +3 -0
  60. package/dist/commands/list-directories-command.d.ts.map +1 -0
  61. package/dist/commands/list-directories-command.js +35 -0
  62. package/dist/commands/list-tools-command.d.ts.map +1 -1
  63. package/dist/commands/list-tools-command.js +61 -21
  64. package/dist/commands/manager.d.ts +9 -4
  65. package/dist/commands/manager.d.ts.map +1 -1
  66. package/dist/commands/manager.js +64 -39
  67. package/dist/commands/model-command.d.ts.map +1 -1
  68. package/dist/commands/model-command.js +201 -66
  69. package/dist/commands/paste-command.d.ts +1 -1
  70. package/dist/commands/paste-command.d.ts.map +1 -1
  71. package/dist/commands/paste-command.js +23 -9
  72. package/dist/commands/pickup-command.d.ts +3 -0
  73. package/dist/commands/pickup-command.d.ts.map +1 -0
  74. package/dist/commands/pickup-command.js +109 -0
  75. package/dist/commands/prompt-command.d.ts +19 -1
  76. package/dist/commands/prompt-command.d.ts.map +1 -1
  77. package/dist/commands/prompt-command.js +191 -98
  78. package/dist/commands/remove-directory-command.d.ts +3 -0
  79. package/dist/commands/remove-directory-command.d.ts.map +1 -0
  80. package/dist/commands/remove-directory-command.js +55 -0
  81. package/dist/commands/reset-command.d.ts +1 -1
  82. package/dist/commands/reset-command.d.ts.map +1 -1
  83. package/dist/commands/reset-command.js +8 -5
  84. package/dist/commands/rules-command.d.ts +1 -1
  85. package/dist/commands/rules-command.d.ts.map +1 -1
  86. package/dist/commands/rules-command.js +25 -22
  87. package/dist/commands/save-command.d.ts +1 -1
  88. package/dist/commands/save-command.d.ts.map +1 -1
  89. package/dist/commands/save-command.js +8 -3
  90. package/dist/commands/shell-command.d.ts.map +1 -1
  91. package/dist/commands/shell-command.js +45 -24
  92. package/dist/commands/types.d.ts +9 -7
  93. package/dist/commands/types.d.ts.map +1 -1
  94. package/dist/commands/usage-command.d.ts +1 -1
  95. package/dist/commands/usage-command.d.ts.map +1 -1
  96. package/dist/commands/usage-command.js +18 -7
  97. package/dist/config.d.ts +21 -11
  98. package/dist/config.d.ts.map +1 -1
  99. package/dist/config.js +90 -63
  100. package/dist/execution/index.d.ts +17 -2
  101. package/dist/execution/index.d.ts.map +1 -1
  102. package/dist/execution/index.js +62 -20
  103. package/dist/formatting.d.ts +127 -0
  104. package/dist/formatting.d.ts.map +1 -1
  105. package/dist/formatting.js +201 -0
  106. package/dist/index.d.ts +7 -2
  107. package/dist/index.d.ts.map +1 -1
  108. package/dist/index.js +263 -102
  109. package/dist/logger.d.ts.map +1 -1
  110. package/dist/logger.js +47 -18
  111. package/dist/mentions.d.ts +2 -1
  112. package/dist/mentions.d.ts.map +1 -1
  113. package/dist/mentions.js +16 -1
  114. package/dist/messages.d.ts +11 -0
  115. package/dist/messages.d.ts.map +1 -1
  116. package/dist/messages.js +122 -21
  117. package/dist/middleware/cache.d.ts +3 -0
  118. package/dist/middleware/cache.d.ts.map +1 -0
  119. package/dist/middleware/cache.js +53 -0
  120. package/dist/middleware/index.d.ts +1 -0
  121. package/dist/middleware/index.d.ts.map +1 -1
  122. package/dist/middleware/index.js +1 -0
  123. package/dist/models/ai-config.d.ts +4 -2
  124. package/dist/models/ai-config.d.ts.map +1 -1
  125. package/dist/models/ai-config.js +12 -2
  126. package/dist/models/anthropic-provider.d.ts.map +1 -1
  127. package/dist/models/anthropic-provider.js +3 -67
  128. package/dist/models/deepseek-provider.d.ts.map +1 -1
  129. package/dist/models/deepseek-provider.js +0 -2
  130. package/dist/models/google-provider.d.ts.map +1 -1
  131. package/dist/models/google-provider.js +0 -3
  132. package/dist/models/groq-provider.d.ts.map +1 -1
  133. package/dist/models/groq-provider.js +0 -1
  134. package/dist/models/manager.d.ts +2 -1
  135. package/dist/models/manager.d.ts.map +1 -1
  136. package/dist/models/manager.js +26 -2
  137. package/dist/models/openai-provider.d.ts.map +1 -1
  138. package/dist/models/openai-provider.js +0 -4
  139. package/dist/models/openrouter-provider.d.ts +16 -22
  140. package/dist/models/openrouter-provider.d.ts.map +1 -1
  141. package/dist/models/openrouter-provider.js +175 -236
  142. package/dist/models/providers.d.ts +4 -14
  143. package/dist/models/providers.d.ts.map +1 -1
  144. package/dist/models/providers.js +1 -57
  145. package/dist/models/xai-provider.d.ts.map +1 -1
  146. package/dist/models/xai-provider.js +0 -2
  147. package/dist/prompts.d.ts +10 -4
  148. package/dist/prompts.d.ts.map +1 -1
  149. package/dist/prompts.js +447 -70
  150. package/dist/repl/project-status-line.d.ts +3 -0
  151. package/dist/repl/project-status-line.d.ts.map +1 -0
  152. package/dist/repl/project-status-line.js +61 -0
  153. package/dist/repl/tool-call-repair.d.ts.map +1 -1
  154. package/dist/repl/tool-call-repair.js +8 -4
  155. package/dist/repl-new.d.ts +51 -0
  156. package/dist/repl-new.d.ts.map +1 -0
  157. package/dist/repl-new.js +354 -0
  158. package/dist/skills.d.ts +20 -0
  159. package/dist/skills.d.ts.map +1 -0
  160. package/dist/skills.js +192 -0
  161. package/dist/terminal/control.d.ts +55 -0
  162. package/dist/terminal/control.d.ts.map +1 -0
  163. package/dist/terminal/control.js +109 -0
  164. package/dist/terminal/default-theme.d.ts +1 -1
  165. package/dist/terminal/default-theme.d.ts.map +1 -1
  166. package/dist/terminal/default-theme.js +24 -28
  167. package/dist/terminal/formatting.d.ts +23 -25
  168. package/dist/terminal/formatting.d.ts.map +1 -1
  169. package/dist/terminal/formatting.js +35 -52
  170. package/dist/terminal/highlight/index.d.ts.map +1 -1
  171. package/dist/terminal/highlight/index.js +3 -6
  172. package/dist/terminal/highlight/theme.d.ts.map +1 -1
  173. package/dist/terminal/highlight/theme.js +2 -6
  174. package/dist/terminal/index.d.ts +2 -94
  175. package/dist/terminal/index.d.ts.map +1 -1
  176. package/dist/terminal/index.js +2 -370
  177. package/dist/terminal/markdown.js +10 -5
  178. package/dist/terminal/select-prompt.d.ts +2 -2
  179. package/dist/terminal/select-prompt.d.ts.map +1 -1
  180. package/dist/terminal/select-prompt.js +47 -39
  181. package/dist/terminal/strip-ansi.js +4 -4
  182. package/dist/terminal/table/cell.d.ts +114 -0
  183. package/dist/terminal/table/cell.d.ts.map +1 -0
  184. package/dist/terminal/table/cell.js +407 -0
  185. package/dist/terminal/table/debug.d.ts +15 -0
  186. package/dist/terminal/table/debug.d.ts.map +1 -0
  187. package/dist/terminal/table/debug.js +32 -0
  188. package/dist/terminal/table/index.d.ts +3 -0
  189. package/dist/terminal/table/index.d.ts.map +1 -0
  190. package/dist/terminal/table/index.js +2 -0
  191. package/dist/terminal/table/layout-manager.d.ts +27 -0
  192. package/dist/terminal/table/layout-manager.d.ts.map +1 -0
  193. package/dist/terminal/table/layout-manager.js +257 -0
  194. package/dist/terminal/table/table.d.ts +9 -0
  195. package/dist/terminal/table/table.d.ts.map +1 -0
  196. package/dist/terminal/table/table.js +97 -0
  197. package/dist/terminal/table/utils.d.ts +63 -0
  198. package/dist/terminal/table/utils.d.ts.map +1 -0
  199. package/dist/terminal/table/utils.js +326 -0
  200. package/dist/tokens/threshold.d.ts +20 -0
  201. package/dist/tokens/threshold.d.ts.map +1 -0
  202. package/dist/tokens/threshold.js +67 -0
  203. package/dist/tools/advanced-edit-file.d.ts +69 -0
  204. package/dist/tools/advanced-edit-file.d.ts.map +1 -0
  205. package/dist/tools/advanced-edit-file.js +285 -0
  206. package/dist/tools/agent.d.ts +16 -5
  207. package/dist/tools/agent.d.ts.map +1 -1
  208. package/dist/tools/agent.js +86 -59
  209. package/dist/tools/bash.d.ts +23 -12
  210. package/dist/tools/bash.d.ts.map +1 -1
  211. package/dist/tools/bash.js +243 -128
  212. package/dist/tools/batch.d.ts +34 -0
  213. package/dist/tools/batch.d.ts.map +1 -0
  214. package/dist/tools/batch.js +174 -0
  215. package/dist/tools/code-interpreter.d.ts +21 -9
  216. package/dist/tools/code-interpreter.d.ts.map +1 -1
  217. package/dist/tools/code-interpreter.js +151 -134
  218. package/dist/tools/delete-file.d.ts +17 -10
  219. package/dist/tools/delete-file.d.ts.map +1 -1
  220. package/dist/tools/delete-file.js +60 -97
  221. package/dist/tools/directory-tree.d.ts +17 -12
  222. package/dist/tools/directory-tree.d.ts.map +1 -1
  223. package/dist/tools/directory-tree.js +57 -48
  224. package/dist/tools/dynamic-tool-loader.d.ts +16 -10
  225. package/dist/tools/dynamic-tool-loader.d.ts.map +1 -1
  226. package/dist/tools/dynamic-tool-loader.js +122 -130
  227. package/dist/tools/dynamic-tool-parser.d.ts +1 -0
  228. package/dist/tools/dynamic-tool-parser.d.ts.map +1 -1
  229. package/dist/tools/dynamic-tool-parser.js +1 -0
  230. package/dist/tools/edit-file.d.ts +35 -15
  231. package/dist/tools/edit-file.d.ts.map +1 -1
  232. package/dist/tools/edit-file.js +127 -114
  233. package/dist/tools/glob.d.ts +36 -0
  234. package/dist/tools/glob.d.ts.map +1 -0
  235. package/dist/tools/glob.js +154 -0
  236. package/dist/tools/grep.d.ts +73 -12
  237. package/dist/tools/grep.d.ts.map +1 -1
  238. package/dist/tools/grep.js +425 -165
  239. package/dist/tools/index.d.ts +220 -126
  240. package/dist/tools/index.d.ts.map +1 -1
  241. package/dist/tools/index.js +284 -135
  242. package/dist/tools/llm-edit-fixer.d.ts +24 -0
  243. package/dist/tools/llm-edit-fixer.d.ts.map +1 -0
  244. package/dist/tools/llm-edit-fixer.js +136 -0
  245. package/dist/tools/move-file.d.ts +19 -7
  246. package/dist/tools/move-file.d.ts.map +1 -1
  247. package/dist/tools/move-file.js +48 -34
  248. package/dist/tools/read-file.d.ts +47 -9
  249. package/dist/tools/read-file.d.ts.map +1 -1
  250. package/dist/tools/read-file.js +84 -70
  251. package/dist/tools/read-multiple-files.d.ts +17 -6
  252. package/dist/tools/read-multiple-files.d.ts.map +1 -1
  253. package/dist/tools/read-multiple-files.js +132 -72
  254. package/dist/tools/save-file.d.ts +45 -12
  255. package/dist/tools/save-file.d.ts.map +1 -1
  256. package/dist/tools/save-file.js +76 -101
  257. package/dist/tools/think.d.ts +15 -7
  258. package/dist/tools/think.d.ts.map +1 -1
  259. package/dist/tools/think.js +34 -20
  260. package/dist/tools/types.d.ts +8 -10
  261. package/dist/tools/types.d.ts.map +1 -1
  262. package/dist/tools/types.js +9 -0
  263. package/dist/tools/utils.d.ts +14 -0
  264. package/dist/tools/utils.d.ts.map +1 -0
  265. package/dist/tools/utils.js +16 -0
  266. package/dist/tools/web-fetch.d.ts +11 -4
  267. package/dist/tools/web-fetch.d.ts.map +1 -1
  268. package/dist/tools/web-fetch.js +39 -38
  269. package/dist/tools/web-search.d.ts +15 -6
  270. package/dist/tools/web-search.d.ts.map +1 -1
  271. package/dist/tools/web-search.js +64 -31
  272. package/dist/tui/autocomplete.d.ts +44 -0
  273. package/dist/tui/autocomplete.d.ts.map +1 -0
  274. package/dist/tui/autocomplete.js +466 -0
  275. package/dist/tui/components/assistant-message.d.ts +18 -0
  276. package/dist/tui/components/assistant-message.d.ts.map +1 -0
  277. package/dist/tui/components/assistant-message.js +29 -0
  278. package/dist/tui/components/box.d.ts +20 -0
  279. package/dist/tui/components/box.d.ts.map +1 -0
  280. package/dist/tui/components/box.js +81 -0
  281. package/dist/tui/components/editor.d.ts +106 -0
  282. package/dist/tui/components/editor.d.ts.map +1 -0
  283. package/dist/tui/components/editor.js +1220 -0
  284. package/dist/tui/components/footer.d.ts +12 -0
  285. package/dist/tui/components/footer.d.ts.map +1 -0
  286. package/dist/tui/components/footer.js +209 -0
  287. package/dist/tui/components/header.d.ts +21 -0
  288. package/dist/tui/components/header.d.ts.map +1 -0
  289. package/dist/tui/components/header.js +63 -0
  290. package/dist/tui/components/input.d.ts +14 -0
  291. package/dist/tui/components/input.d.ts.map +1 -0
  292. package/dist/tui/components/input.js +122 -0
  293. package/dist/tui/components/loader.d.ts +23 -0
  294. package/dist/tui/components/loader.d.ts.map +1 -0
  295. package/dist/tui/components/loader.js +45 -0
  296. package/dist/tui/components/markdown.d.ts +106 -0
  297. package/dist/tui/components/markdown.d.ts.map +1 -0
  298. package/dist/tui/components/markdown.js +586 -0
  299. package/dist/tui/components/modal.d.ts +29 -0
  300. package/dist/tui/components/modal.d.ts.map +1 -0
  301. package/dist/tui/components/modal.js +263 -0
  302. package/dist/tui/components/progress-bar.d.ts +19 -0
  303. package/dist/tui/components/progress-bar.d.ts.map +1 -0
  304. package/dist/tui/components/progress-bar.js +78 -0
  305. package/dist/tui/components/prompt-status.d.ts +17 -0
  306. package/dist/tui/components/prompt-status.d.ts.map +1 -0
  307. package/dist/tui/components/prompt-status.js +26 -0
  308. package/dist/tui/components/select-list.d.ts +48 -0
  309. package/dist/tui/components/select-list.d.ts.map +1 -0
  310. package/dist/tui/components/select-list.js +207 -0
  311. package/dist/tui/components/spacer.d.ts +16 -0
  312. package/dist/tui/components/spacer.d.ts.map +1 -0
  313. package/dist/tui/components/spacer.js +27 -0
  314. package/dist/tui/components/table.d.ts +27 -0
  315. package/dist/tui/components/table.d.ts.map +1 -0
  316. package/dist/tui/components/table.js +125 -0
  317. package/dist/tui/components/text.d.ts +26 -0
  318. package/dist/tui/components/text.d.ts.map +1 -0
  319. package/dist/tui/components/text.js +143 -0
  320. package/dist/tui/components/thinking-block.d.ts +14 -0
  321. package/dist/tui/components/thinking-block.d.ts.map +1 -0
  322. package/dist/tui/components/thinking-block.js +33 -0
  323. package/dist/tui/components/tool-execution.d.ts +21 -0
  324. package/dist/tui/components/tool-execution.d.ts.map +1 -0
  325. package/dist/tui/components/tool-execution.js +161 -0
  326. package/dist/tui/components/user-message.d.ts +9 -0
  327. package/dist/tui/components/user-message.d.ts.map +1 -0
  328. package/dist/tui/components/user-message.js +23 -0
  329. package/dist/tui/components/welcome.d.ts +6 -0
  330. package/dist/tui/components/welcome.d.ts.map +1 -0
  331. package/dist/tui/components/welcome.js +30 -0
  332. package/dist/tui/index.d.ts +18 -0
  333. package/dist/tui/index.d.ts.map +1 -0
  334. package/dist/tui/index.js +22 -0
  335. package/dist/tui/terminal.d.ts +38 -0
  336. package/dist/tui/terminal.d.ts.map +1 -0
  337. package/dist/tui/terminal.js +94 -0
  338. package/dist/tui/tui.d.ts +69 -0
  339. package/dist/tui/tui.d.ts.map +1 -0
  340. package/dist/tui/tui.js +204 -0
  341. package/dist/tui/utils.d.ts +24 -0
  342. package/dist/tui/utils.d.ts.map +1 -0
  343. package/dist/tui/utils.js +111 -0
  344. package/dist/utils/bash.d.ts +7 -0
  345. package/dist/utils/bash.d.ts.map +1 -0
  346. package/dist/{tools/bash-utils.js → utils/bash.js} +31 -12
  347. package/dist/utils/{filesystem.d.ts → filesystem/operations.d.ts} +1 -1
  348. package/dist/utils/filesystem/operations.d.ts.map +1 -0
  349. package/dist/utils/filesystem/security.d.ts +9 -0
  350. package/dist/utils/filesystem/security.d.ts.map +1 -0
  351. package/dist/{tools/filesystem-utils.js → utils/filesystem/security.js} +93 -21
  352. package/dist/utils/funcs.d.ts +6 -0
  353. package/dist/utils/funcs.d.ts.map +1 -0
  354. package/dist/utils/funcs.js +6 -0
  355. package/dist/utils/generators.d.ts +3 -0
  356. package/dist/utils/generators.d.ts.map +1 -0
  357. package/dist/utils/generators.js +25 -0
  358. package/dist/{tools/git-utils.d.ts → utils/git.d.ts} +1 -1
  359. package/dist/utils/git.d.ts.map +1 -0
  360. package/dist/{tools/git-utils.js → utils/git.js} +0 -6
  361. package/dist/utils/glob.js +1 -1
  362. package/dist/utils/iterables.d.ts +2 -0
  363. package/dist/utils/iterables.d.ts.map +1 -0
  364. package/dist/utils/iterables.js +6 -0
  365. package/dist/utils/{zod-utils.d.ts → zod.d.ts} +1 -1
  366. package/dist/utils/zod.d.ts.map +1 -0
  367. package/package.json +21 -21
  368. package/dist/conversation-analyzer.d.ts +0 -11
  369. package/dist/conversation-analyzer.d.ts.map +0 -1
  370. package/dist/conversation-analyzer.js +0 -88
  371. package/dist/repl/display-tool-messages.d.ts +0 -4
  372. package/dist/repl/display-tool-messages.d.ts.map +0 -1
  373. package/dist/repl/display-tool-messages.js +0 -55
  374. package/dist/repl/display-tool-use.d.ts +0 -14
  375. package/dist/repl/display-tool-use.d.ts.map +0 -1
  376. package/dist/repl/display-tool-use.js +0 -63
  377. package/dist/repl/get-prompt-header.d.ts +0 -8
  378. package/dist/repl/get-prompt-header.d.ts.map +0 -1
  379. package/dist/repl/get-prompt-header.js +0 -38
  380. package/dist/repl-prompt.d.ts +0 -15
  381. package/dist/repl-prompt.d.ts.map +0 -1
  382. package/dist/repl-prompt.js +0 -147
  383. package/dist/repl.d.ts +0 -31
  384. package/dist/repl.d.ts.map +0 -1
  385. package/dist/repl.js +0 -310
  386. package/dist/terminal/checkbox-prompt.d.ts +0 -36
  387. package/dist/terminal/checkbox-prompt.d.ts.map +0 -1
  388. package/dist/terminal/checkbox-prompt.js +0 -362
  389. package/dist/terminal/editor-prompt.d.ts +0 -10
  390. package/dist/terminal/editor-prompt.d.ts.map +0 -1
  391. package/dist/terminal/editor-prompt.js +0 -61
  392. package/dist/terminal/errors.d.ts +0 -19
  393. package/dist/terminal/errors.d.ts.map +0 -1
  394. package/dist/terminal/errors.js +0 -37
  395. package/dist/terminal/input-prompt.d.ts +0 -16
  396. package/dist/terminal/input-prompt.d.ts.map +0 -1
  397. package/dist/terminal/input-prompt.js +0 -181
  398. package/dist/terminal/search-prompt.d.ts +0 -20
  399. package/dist/terminal/search-prompt.d.ts.map +0 -1
  400. package/dist/terminal/search-prompt.js +0 -279
  401. package/dist/terminal/types.d.ts +0 -35
  402. package/dist/terminal/types.d.ts.map +0 -1
  403. package/dist/terminal/types.js +0 -1
  404. package/dist/tokens/manage-output.d.ts +0 -34
  405. package/dist/tokens/manage-output.d.ts.map +0 -1
  406. package/dist/tokens/manage-output.js +0 -44
  407. package/dist/tool-executor.d.ts +0 -28
  408. package/dist/tool-executor.d.ts.map +0 -1
  409. package/dist/tool-executor.js +0 -74
  410. package/dist/tools/bash-utils.d.ts +0 -7
  411. package/dist/tools/bash-utils.d.ts.map +0 -1
  412. package/dist/tools/file-editing-utils.d.ts +0 -2
  413. package/dist/tools/file-editing-utils.d.ts.map +0 -1
  414. package/dist/tools/file-editing-utils.js +0 -135
  415. package/dist/tools/filesystem-utils.d.ts +0 -7
  416. package/dist/tools/filesystem-utils.d.ts.map +0 -1
  417. package/dist/tools/git-utils.d.ts.map +0 -1
  418. package/dist/utils/filesystem.d.ts.map +0 -1
  419. package/dist/utils/zod-utils.d.ts.map +0 -1
  420. /package/dist/utils/{filesystem.js → filesystem/operations.js} +0 -0
  421. /package/dist/utils/{zod-utils.js → zod.js} +0 -0
@@ -0,0 +1,1220 @@
1
+ import style from "../../terminal/style.js";
2
+ import { visibleWidth } from "../utils.js";
3
+ import { isNavigationKey, isTab, SelectList } from "./select-list.js";
4
+ // Grapheme segmenter for proper Unicode iteration (handles emojis, etc.)
5
+ const segmenter = new Intl.Segmenter();
6
+ // Cache for line metrics to avoid repeated segmentation
7
+ const lineMetricsCache = {
8
+ maxSize: 1000,
9
+ cache: new Map(),
10
+ get(line) {
11
+ let cached = this.cache.get(line);
12
+ if (!cached) {
13
+ // Fast path for ASCII-only lines (common case)
14
+ if (/^[\x20-\x7E\t]*$/.test(line)) {
15
+ // ASCII characters (including tabs)
16
+ const graphemes = line.split(""); // Simple split for ASCII
17
+ const widths = graphemes.map((char) => (char === "\t" ? 3 : 1));
18
+ const totalWidth = widths.reduce((sum, w) => sum + w, 0);
19
+ cached = { graphemes, widths, totalWidth };
20
+ }
21
+ else {
22
+ // Complex Unicode line, use full segmentation
23
+ const graphemes = [...segmenter.segment(line)].map((seg) => seg.segment);
24
+ const widths = graphemes.map((g) => visibleWidth(g));
25
+ const totalWidth = widths.reduce((sum, w) => sum + w, 0);
26
+ cached = { graphemes, widths, totalWidth };
27
+ }
28
+ this.cache.set(line, cached);
29
+ // Enforce size limit
30
+ if (this.cache.size > this.maxSize) {
31
+ // Delete first (oldest) entry - simple but not LRU; okay for our use case
32
+ const firstKey = this.cache.keys().next().value;
33
+ if (firstKey !== undefined) {
34
+ this.cache.delete(firstKey);
35
+ }
36
+ }
37
+ }
38
+ return cached;
39
+ },
40
+ clear() {
41
+ this.cache.clear();
42
+ },
43
+ };
44
+ /**
45
+ * Text editor component with support for multi-line input and autocomplete.
46
+ *
47
+ * Key bindings:
48
+ * - Enter: Create new line
49
+ * - Shift+Enter / Ctrl+Enter / Option+Enter: Submit prompt
50
+ * - Tab: Trigger autocomplete
51
+ * - Escape: Cancel autocomplete or custom handler
52
+ * - Ctrl+C: Custom handler
53
+ * - Arrow keys: Navigate text
54
+ * - Backspace/Delete: Delete characters
55
+ * - Ctrl+A: Move to start of line
56
+ * - Ctrl+E: Move to end of line
57
+ * - Ctrl+K: Delete to end of line
58
+ * - Ctrl+U: Delete to start of line
59
+ * - Ctrl+W / Option+Backspace: Delete word backwards
60
+ * - Ctrl+Left/Right / Option+Left/Right: Word navigation
61
+ * - Up/Down: History navigation when editor is empty
62
+ */
63
+ export class Editor {
64
+ state = {
65
+ lines: [""],
66
+ cursorLine: 0,
67
+ cursorCol: 0,
68
+ };
69
+ theme;
70
+ // Store last render width for cursor navigation
71
+ lastWidth = 80;
72
+ // Border color (can be changed dynamically)
73
+ borderColor;
74
+ // Autocomplete support
75
+ autocompleteProvider;
76
+ autocompleteList;
77
+ isAutocompleting = false;
78
+ autocompletePrefix = "";
79
+ autocompleteDebounceTimer;
80
+ // Paste tracking for large pastes
81
+ pastes = new Map();
82
+ pasteCounter = 0;
83
+ // Bracketed paste mode buffering
84
+ pasteBuffer = "";
85
+ isInPaste = false;
86
+ // Prompt history for up/down navigation
87
+ history = [];
88
+ historyIndex = -1; // -1 = not browsing, 0 = most recent, 1 = older, etc.
89
+ onSubmit;
90
+ onChange;
91
+ disableSubmit = false;
92
+ // Custom key handlers for coding-agent
93
+ onEscape;
94
+ onCtrlC;
95
+ onRenderRequested;
96
+ constructor(theme) {
97
+ // Default theme if none provided (backward compatibility)
98
+ this.theme = theme || {
99
+ borderColor: style.gray,
100
+ };
101
+ this.borderColor = this.theme.borderColor;
102
+ }
103
+ /**
104
+ * Add a prompt to history for up/down arrow navigation.
105
+ * Called after successful submission.
106
+ */
107
+ addToHistory(text) {
108
+ const trimmed = text.trim();
109
+ if (!trimmed)
110
+ return;
111
+ // Don't add consecutive duplicates
112
+ if (this.history.length > 0 && this.history[0] === trimmed)
113
+ return;
114
+ this.history.unshift(trimmed);
115
+ // Limit history size
116
+ if (this.history.length > 100) {
117
+ this.history.pop();
118
+ }
119
+ }
120
+ setAutocompleteProvider(provider) {
121
+ this.autocompleteProvider = provider;
122
+ }
123
+ isEditorEmpty() {
124
+ return this.state.lines.length === 1 && this.state.lines[0] === "";
125
+ }
126
+ isOnFirstVisualLine() {
127
+ const visualLines = this.buildVisualLineMap(this.lastWidth);
128
+ const currentVisualLine = this.findCurrentVisualLine(visualLines);
129
+ return currentVisualLine === 0;
130
+ }
131
+ isOnLastVisualLine() {
132
+ const visualLines = this.buildVisualLineMap(this.lastWidth);
133
+ const currentVisualLine = this.findCurrentVisualLine(visualLines);
134
+ return currentVisualLine === visualLines.length - 1;
135
+ }
136
+ navigateHistory(direction) {
137
+ if (this.history.length === 0)
138
+ return;
139
+ const newIndex = this.historyIndex - direction; // Up(-1) increases index, Down(1) decreases
140
+ if (newIndex < -1 || newIndex >= this.history.length)
141
+ return;
142
+ this.historyIndex = newIndex;
143
+ if (this.historyIndex === -1) {
144
+ // Returned to "current" state - clear editor
145
+ this.setTextInternal("");
146
+ }
147
+ else {
148
+ this.setTextInternal(this.history[this.historyIndex] || "");
149
+ }
150
+ }
151
+ /** Internal setText that doesn't reset history state - used by navigateHistory */
152
+ setTextInternal(text) {
153
+ const lines = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
154
+ this.state.lines = lines.length === 0 ? [""] : lines;
155
+ this.state.cursorLine = this.state.lines.length - 1;
156
+ this.state.cursorCol = this.state.lines[this.state.cursorLine]?.length || 0;
157
+ if (this.onChange) {
158
+ this.onChange(this.getText());
159
+ }
160
+ }
161
+ invalidate() {
162
+ // No cached state to invalidate currently
163
+ }
164
+ render(width) {
165
+ // Store width for cursor navigation
166
+ this.lastWidth = width;
167
+ const horizontal = this.borderColor("─");
168
+ // Layout the text - use full width
169
+ const layoutLines = this.layoutText(width);
170
+ const result = [];
171
+ // Render top border
172
+ result.push(horizontal.repeat(width));
173
+ // Render each layout line
174
+ for (const layoutLine of layoutLines) {
175
+ let displayText = layoutLine.text;
176
+ let lineVisibleWidth = layoutLine.width;
177
+ // Add cursor if this line has it
178
+ if (layoutLine.hasCursor && layoutLine.cursorPos !== undefined) {
179
+ const before = displayText.slice(0, layoutLine.cursorPos);
180
+ const after = displayText.slice(layoutLine.cursorPos);
181
+ if (after.length > 0) {
182
+ // Cursor is on a character (grapheme) - replace it with highlighted version
183
+ // Get the first grapheme from 'after'
184
+ const afterGraphemes = [...segmenter.segment(after)];
185
+ const firstGrapheme = afterGraphemes[0]?.segment || "";
186
+ const restAfter = after.slice(firstGrapheme.length);
187
+ const cursor = `\x1b[7m${firstGrapheme}\x1b[0m`;
188
+ displayText = before + cursor + restAfter;
189
+ // lineVisibleWidth stays the same - we're replacing, not adding
190
+ }
191
+ else {
192
+ // Cursor is at the end - check if we have room for the space
193
+ if (lineVisibleWidth < width) {
194
+ // We have room - add highlighted space
195
+ const cursor = "\x1b[7m \x1b[0m";
196
+ displayText = before + cursor;
197
+ // lineVisibleWidth increases by 1 - we're adding a space
198
+ lineVisibleWidth = lineVisibleWidth + 1;
199
+ }
200
+ else {
201
+ // Line is at full width - use reverse video on last grapheme if possible
202
+ // or just show cursor at the end without adding space
203
+ const beforeGraphemes = [...segmenter.segment(before)];
204
+ if (beforeGraphemes.length > 0) {
205
+ const lastGrapheme = beforeGraphemes[beforeGraphemes.length - 1]?.segment || "";
206
+ const cursor = `\x1b[7m${lastGrapheme}\x1b[0m`;
207
+ // Rebuild 'before' without the last grapheme
208
+ const beforeWithoutLast = beforeGraphemes
209
+ .slice(0, -1)
210
+ .map((g) => g.segment)
211
+ .join("");
212
+ displayText = beforeWithoutLast + cursor;
213
+ }
214
+ // lineVisibleWidth stays the same
215
+ }
216
+ }
217
+ }
218
+ // Calculate padding based on actual visible width
219
+ const padding = " ".repeat(Math.max(0, width - lineVisibleWidth));
220
+ // Render the line (no side borders, just horizontal lines above and below)
221
+ result.push(displayText + padding);
222
+ }
223
+ // Render bottom border
224
+ result.push(horizontal.repeat(width));
225
+ // Add autocomplete list if active
226
+ if (this.isAutocompleting && this.autocompleteList) {
227
+ const autocompleteResult = this.autocompleteList.render(width);
228
+ result.push(...autocompleteResult);
229
+ }
230
+ return result;
231
+ }
232
+ handleInput(data) {
233
+ // Handle bracketed paste mode
234
+ // Start of paste: \x1b[200~
235
+ // End of paste: \x1b[201~
236
+ // Check if we're starting a bracketed paste
237
+ if (data.includes("\x1b[200~")) {
238
+ this.isInPaste = true;
239
+ this.pasteBuffer = "";
240
+ // Remove the start marker and keep the rest
241
+ const cleanedData = data.replace("\x1b[200~", "");
242
+ // Process the remaining data
243
+ this.processInputData(cleanedData);
244
+ return;
245
+ }
246
+ // If we're in a paste, buffer the data
247
+ if (this.isInPaste) {
248
+ // Append data to buffer first (end marker could be split across chunks)
249
+ this.pasteBuffer += data;
250
+ // Check if the accumulated buffer contains the end marker
251
+ const endIndex = this.pasteBuffer.indexOf("\x1b[201~");
252
+ if (endIndex !== -1) {
253
+ // Extract content before the end marker
254
+ const pasteContent = this.pasteBuffer.substring(0, endIndex);
255
+ // Process the complete paste
256
+ this.handlePaste(pasteContent);
257
+ // Reset paste state
258
+ this.isInPaste = false;
259
+ // Process any remaining data after the end marker
260
+ const remaining = this.pasteBuffer.substring(endIndex + 6); // 6 = length of \x1b[201~
261
+ this.pasteBuffer = "";
262
+ if (remaining.length > 0) {
263
+ this.handleInput(remaining);
264
+ }
265
+ return;
266
+ }
267
+ // Still accumulating, wait for more data
268
+ return;
269
+ }
270
+ // Intercept Escape key - but only if autocomplete is NOT active
271
+ // (let parent handle escape for autocomplete cancellation)
272
+ if (data === "\x1b" && this.onEscape && !this.isShowingAutocomplete()) {
273
+ this.onEscape();
274
+ return;
275
+ }
276
+ // Intercept Ctrl+C
277
+ if (data === "\x03" && this.onCtrlC) {
278
+ this.onCtrlC();
279
+ return;
280
+ }
281
+ // Process regular input data
282
+ this.processInputData(data);
283
+ }
284
+ processInputData(data) {
285
+ // Handle special key combinations first
286
+ // Ctrl+C - Exit (let parent handle this)
287
+ if (data.charCodeAt(0) === 3) {
288
+ return;
289
+ }
290
+ // Handle autocomplete special keys first (but don't block other input)
291
+ if (this.isAutocompleting && this.autocompleteList) {
292
+ // Escape - cancel autocomplete
293
+ if (data === "\x1b") {
294
+ this.cancelAutocomplete();
295
+ return;
296
+ }
297
+ // Enter - apply selection
298
+ if (data === "\r") {
299
+ const selected = this.autocompleteList.getSelectedItem();
300
+ if (selected && this.autocompleteProvider) {
301
+ const result = this.autocompleteProvider.applyCompletion(this.state.lines, this.state.cursorLine, this.state.cursorCol, selected, this.autocompletePrefix);
302
+ this.state.lines = result.lines;
303
+ this.state.cursorLine = result.cursorLine;
304
+ this.state.cursorCol = result.cursorCol;
305
+ this.cancelAutocomplete();
306
+ if (this.onChange) {
307
+ this.onChange(this.getText());
308
+ }
309
+ }
310
+ return;
311
+ }
312
+ // Navigation keys (arrows, Tab, Shift+Tab) - pass to autocomplete list
313
+ if (isNavigationKey(data)) {
314
+ this.autocompleteList.handleInput(data);
315
+ return;
316
+ }
317
+ // For other keys (like regular typing), DON'T return here
318
+ // Let them fall through to normal character handling
319
+ }
320
+ // Tab key - context-aware completion (but not when already autocompleting)
321
+ if (isTab(data) && !this.isAutocompleting) {
322
+ void this.handleTabCompletion();
323
+ return;
324
+ }
325
+ // Continue with rest of input handling
326
+ // Ctrl+K - Delete to end of line
327
+ if (data.charCodeAt(0) === 11) {
328
+ this.deleteToEndOfLine();
329
+ }
330
+ // Ctrl+U - Delete to start of line
331
+ else if (data.charCodeAt(0) === 21) {
332
+ this.deleteToStartOfLine();
333
+ }
334
+ // Ctrl+W - Delete word backwards
335
+ else if (data.charCodeAt(0) === 23) {
336
+ this.deleteWordBackwards();
337
+ }
338
+ // Option/Alt+Backspace (e.g. Ghostty sends ESC + DEL)
339
+ else if (data === "\x1b\x7f") {
340
+ this.deleteWordBackwards();
341
+ }
342
+ // Ctrl+A - Move to start of line
343
+ else if (data.charCodeAt(0) === 1) {
344
+ this.moveToLineStart();
345
+ }
346
+ // Ctrl+E - Move to end of line
347
+ else if (data.charCodeAt(0) === 5) {
348
+ this.moveToLineEnd();
349
+ }
350
+ // Plain Enter (char code 13 for CR) - create new line
351
+ else if (data.charCodeAt(0) === 13 && data.length === 1) {
352
+ this.addNewLine();
353
+ }
354
+ // Modified Enter keys (Shift+Enter, Ctrl+Enter, etc.) - submit
355
+ else if (this.isModifiedEnter(data)) {
356
+ // If submit is disabled, do nothing
357
+ if (this.disableSubmit) {
358
+ return;
359
+ }
360
+ // Get text and substitute paste markers with actual content
361
+ let result = this.state.lines.join("\n").trim();
362
+ // Replace all [paste #N +xxx lines] or [paste #N xxx chars] markers with actual paste content
363
+ for (const [pasteId, pasteContent] of this.pastes) {
364
+ // Match formats: [paste #N], [paste #N +xxx lines], or [paste #N xxx chars]
365
+ const markerRegex = new RegExp(`\\[paste #${pasteId}( (\\+\\d+ lines|\\d+ chars))?\\]`, "g");
366
+ result = result.replace(markerRegex, pasteContent);
367
+ }
368
+ // Reset editor and clear pastes
369
+ this.state = {
370
+ lines: [""],
371
+ cursorLine: 0,
372
+ cursorCol: 0,
373
+ };
374
+ this.pastes.clear();
375
+ this.pasteCounter = 0;
376
+ this.historyIndex = -1; // Exit history browsing mode
377
+ // Notify that editor is now empty
378
+ if (this.onChange) {
379
+ this.onChange("");
380
+ }
381
+ if (this.onSubmit) {
382
+ this.onSubmit(result);
383
+ }
384
+ }
385
+ // Backspace
386
+ else if (data.charCodeAt(0) === 127 || data.charCodeAt(0) === 8) {
387
+ this.handleBackspace();
388
+ }
389
+ // Line navigation shortcuts (Home/End keys)
390
+ else if (data === "\x1b[H" || data === "\x1b[1~" || data === "\x1b[7~") {
391
+ // Home key
392
+ this.moveToLineStart();
393
+ }
394
+ else if (data === "\x1b[F" || data === "\x1b[4~" || data === "\x1b[8~") {
395
+ // End key
396
+ this.moveToLineEnd();
397
+ }
398
+ // Forward delete (Fn+Backspace or Delete key)
399
+ else if (data === "\x1b[3~") {
400
+ // Delete key
401
+ this.handleForwardDelete();
402
+ }
403
+ // Word navigation (Option/Alt + Arrow or Ctrl + Arrow)
404
+ // Option+Left: \x1b[1;3D or \x1bb
405
+ // Option+Right: \x1b[1;3C or \x1bf
406
+ // Ctrl+Left: \x1b[1;5D
407
+ // Ctrl+Right: \x1b[1;5C
408
+ else if (data === "\x1b[1;3D" || data === "\x1bb" || data === "\x1b[1;5D") {
409
+ // Word left
410
+ this.moveWordBackwards();
411
+ }
412
+ else if (data === "\x1b[1;3C" ||
413
+ data === "\x1bf" ||
414
+ data === "\x1b[1;5C") {
415
+ // Word right
416
+ this.moveWordForwards();
417
+ }
418
+ // Arrow keys
419
+ else if (data === "\x1b[A") {
420
+ // Up - history navigation or cursor movement
421
+ if (this.isEditorEmpty()) {
422
+ this.navigateHistory(-1); // Start browsing history
423
+ }
424
+ else if (this.historyIndex > -1 && this.isOnFirstVisualLine()) {
425
+ this.navigateHistory(-1); // Navigate to older history entry
426
+ }
427
+ else {
428
+ this.moveCursor(-1, 0); // Cursor movement (within text or history entry)
429
+ }
430
+ }
431
+ else if (data === "\x1b[B") {
432
+ // Down - history navigation or cursor movement
433
+ if (this.historyIndex > -1 && this.isOnLastVisualLine()) {
434
+ this.navigateHistory(1); // Navigate to newer history entry or clear
435
+ }
436
+ else {
437
+ this.moveCursor(1, 0); // Cursor movement (within text or history entry)
438
+ }
439
+ }
440
+ else if (data === "\x1b[C") {
441
+ // Right
442
+ this.moveCursor(0, 1);
443
+ }
444
+ else if (data === "\x1b[D") {
445
+ // Left
446
+ this.moveCursor(0, -1);
447
+ }
448
+ // Regular characters (printable characters and unicode, but not control characters)
449
+ else if (data.charCodeAt(0) >= 32) {
450
+ this.insertCharacter(data);
451
+ }
452
+ }
453
+ layoutText(contentWidth) {
454
+ const layoutLines = [];
455
+ if (this.state.lines.length === 0 ||
456
+ (this.state.lines.length === 1 && this.state.lines[0] === "")) {
457
+ // Empty editor
458
+ layoutLines.push({
459
+ text: "",
460
+ hasCursor: true,
461
+ cursorPos: 0,
462
+ width: 0,
463
+ });
464
+ return layoutLines;
465
+ }
466
+ // Process each logical line
467
+ for (let i = 0; i < this.state.lines.length; i++) {
468
+ const line = this.state.lines[i] || "";
469
+ const isCurrentLine = i === this.state.cursorLine;
470
+ const metrics = lineMetricsCache.get(line);
471
+ const lineVisibleWidth = metrics.totalWidth;
472
+ if (lineVisibleWidth <= contentWidth) {
473
+ // Line fits in one layout line
474
+ if (isCurrentLine) {
475
+ layoutLines.push({
476
+ text: line,
477
+ hasCursor: true,
478
+ cursorPos: this.state.cursorCol,
479
+ width: lineVisibleWidth,
480
+ });
481
+ }
482
+ else {
483
+ layoutLines.push({
484
+ text: line,
485
+ hasCursor: false,
486
+ width: lineVisibleWidth,
487
+ });
488
+ }
489
+ }
490
+ else {
491
+ // Line needs wrapping - use cached graphemes and widths
492
+ const chunks = [];
493
+ let currentChunk = "";
494
+ let currentWidth = 0;
495
+ let chunkStartIndex = 0;
496
+ let currentIndex = 0;
497
+ for (let g = 0; g < metrics.graphemes.length; g++) {
498
+ const grapheme = metrics.graphemes[g];
499
+ const graphemeWidth = metrics.widths[g];
500
+ if (currentWidth + graphemeWidth > contentWidth &&
501
+ currentChunk !== "") {
502
+ // Start a new chunk
503
+ chunks.push({
504
+ text: currentChunk,
505
+ startIndex: chunkStartIndex,
506
+ endIndex: currentIndex,
507
+ width: currentWidth,
508
+ });
509
+ currentChunk = grapheme;
510
+ currentWidth = graphemeWidth;
511
+ chunkStartIndex = currentIndex;
512
+ }
513
+ else {
514
+ currentChunk += grapheme;
515
+ currentWidth += graphemeWidth;
516
+ }
517
+ currentIndex += grapheme.length;
518
+ }
519
+ // Push the last chunk
520
+ if (currentChunk !== "") {
521
+ chunks.push({
522
+ text: currentChunk,
523
+ startIndex: chunkStartIndex,
524
+ endIndex: currentIndex,
525
+ width: currentWidth,
526
+ });
527
+ }
528
+ for (let chunkIndex = 0; chunkIndex < chunks.length; chunkIndex++) {
529
+ const chunk = chunks[chunkIndex];
530
+ if (!chunk)
531
+ continue;
532
+ const cursorPos = this.state.cursorCol;
533
+ const isLastChunk = chunkIndex === chunks.length - 1;
534
+ // For non-last chunks, cursor at endIndex belongs to the next chunk
535
+ const hasCursorInChunk = isCurrentLine &&
536
+ cursorPos >= chunk.startIndex &&
537
+ (isLastChunk
538
+ ? cursorPos <= chunk.endIndex
539
+ : cursorPos < chunk.endIndex);
540
+ if (hasCursorInChunk) {
541
+ layoutLines.push({
542
+ text: chunk.text,
543
+ hasCursor: true,
544
+ cursorPos: cursorPos - chunk.startIndex,
545
+ width: chunk.width,
546
+ });
547
+ }
548
+ else {
549
+ layoutLines.push({
550
+ text: chunk.text,
551
+ hasCursor: false,
552
+ width: chunk.width,
553
+ });
554
+ }
555
+ }
556
+ }
557
+ }
558
+ return layoutLines;
559
+ }
560
+ getText() {
561
+ return this.state.lines.join("\n");
562
+ }
563
+ setText(text) {
564
+ this.historyIndex = -1; // Exit history browsing mode
565
+ this.setTextInternal(text);
566
+ }
567
+ // All the editor methods from before...
568
+ insertCharacter(char) {
569
+ this.historyIndex = -1; // Exit history browsing mode
570
+ const line = this.state.lines[this.state.cursorLine] || "";
571
+ const before = line.slice(0, this.state.cursorCol);
572
+ const after = line.slice(this.state.cursorCol);
573
+ this.state.lines[this.state.cursorLine] = before + char + after;
574
+ this.state.cursorCol += char.length; // Fix: increment by the length of the inserted string
575
+ if (this.onChange) {
576
+ this.onChange(this.getText());
577
+ }
578
+ // Check if we should trigger or update autocomplete
579
+ if (!this.isAutocompleting) {
580
+ // Auto-trigger for "/" at the start of a line (slash commands)
581
+ if (char === "/" && this.isAtStartOfMessage()) {
582
+ void this.tryTriggerAutocomplete();
583
+ }
584
+ // Auto-trigger for "@" file reference (fuzzy search)
585
+ else if (char === "@") {
586
+ const currentLine = this.state.lines[this.state.cursorLine] || "";
587
+ const textBeforeCursor = currentLine.slice(0, this.state.cursorCol);
588
+ // Only trigger if @ is after whitespace or at start of line
589
+ const charBeforeAt = textBeforeCursor[textBeforeCursor.length - 2];
590
+ if (textBeforeCursor.length === 1 ||
591
+ charBeforeAt === " " ||
592
+ charBeforeAt === "\t") {
593
+ void this.tryTriggerAutocomplete();
594
+ }
595
+ }
596
+ // Also auto-trigger when typing letters in a slash command context
597
+ else if (/[a-zA-Z0-9]/.test(char)) {
598
+ const currentLine = this.state.lines[this.state.cursorLine] || "";
599
+ const textBeforeCursor = currentLine.slice(0, this.state.cursorCol);
600
+ // Check if we're in a slash command (with or without space for arguments)
601
+ if (textBeforeCursor.trimStart().startsWith("/")) {
602
+ void this.tryTriggerAutocomplete();
603
+ }
604
+ // Check if we're in an @ file reference context
605
+ else if (textBeforeCursor.match(/(?:^|[\s])@[^\s]*$/)) {
606
+ void this.tryTriggerAutocomplete();
607
+ }
608
+ }
609
+ }
610
+ else {
611
+ void this.updateAutocomplete();
612
+ }
613
+ }
614
+ handlePaste(pastedText) {
615
+ // Clean the pasted text
616
+ const cleanText = pastedText.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
617
+ // Convert tabs to spaces (4 spaces per tab)
618
+ const tabExpandedText = cleanText.replace(/\t/g, " ");
619
+ // Filter out non-printable characters except newlines
620
+ const filteredText = tabExpandedText
621
+ .split("")
622
+ .filter((char) => char === "\n" || (char >= " " && char <= "~"))
623
+ .join("");
624
+ // Split into lines
625
+ const pastedLines = filteredText.split("\n");
626
+ // Check if this is a large paste (> 10 lines or > 1000 characters)
627
+ const totalChars = filteredText.length;
628
+ if (pastedLines.length > 10 || totalChars > 1000) {
629
+ // Store the paste and insert a marker
630
+ this.pasteCounter++;
631
+ const pasteId = this.pasteCounter;
632
+ this.pastes.set(pasteId, filteredText);
633
+ // Insert marker like "[paste #1 +123 lines]" or "[paste #1 1234 chars]"
634
+ const marker = pastedLines.length > 10
635
+ ? `[paste #${pasteId} +${pastedLines.length} lines]`
636
+ : `[paste #${pasteId} ${totalChars} chars]`;
637
+ for (const char of marker) {
638
+ this.insertCharacter(char);
639
+ }
640
+ return;
641
+ }
642
+ if (pastedLines.length === 1) {
643
+ // Single line - just insert each character
644
+ const text = pastedLines[0] || "";
645
+ for (const char of text) {
646
+ this.insertCharacter(char);
647
+ }
648
+ return;
649
+ }
650
+ // Multi-line paste - be very careful with array manipulation
651
+ const currentLine = this.state.lines[this.state.cursorLine] || "";
652
+ const beforeCursor = currentLine.slice(0, this.state.cursorCol);
653
+ const afterCursor = currentLine.slice(this.state.cursorCol);
654
+ // Build the new lines array step by step
655
+ const newLines = [];
656
+ // Add all lines before current line
657
+ for (let i = 0; i < this.state.cursorLine; i++) {
658
+ newLines.push(this.state.lines[i] || "");
659
+ }
660
+ // Add the first pasted line merged with before cursor text
661
+ newLines.push(beforeCursor + (pastedLines[0] || ""));
662
+ // Add all middle pasted lines
663
+ for (let i = 1; i < pastedLines.length - 1; i++) {
664
+ newLines.push(pastedLines[i] || "");
665
+ }
666
+ // Add the last pasted line with after cursor text
667
+ newLines.push((pastedLines[pastedLines.length - 1] || "") + afterCursor);
668
+ // Add all lines after current line
669
+ for (let i = this.state.cursorLine + 1; i < this.state.lines.length; i++) {
670
+ newLines.push(this.state.lines[i] || "");
671
+ }
672
+ // Replace the entire lines array
673
+ this.state.lines = newLines;
674
+ // Update cursor position to end of pasted content
675
+ this.state.cursorLine += pastedLines.length - 1;
676
+ this.state.cursorCol = (pastedLines[pastedLines.length - 1] || "").length;
677
+ // Notify of change
678
+ if (this.onChange) {
679
+ this.onChange(this.getText());
680
+ }
681
+ }
682
+ addNewLine() {
683
+ const currentLine = this.state.lines[this.state.cursorLine] || "";
684
+ const before = currentLine.slice(0, this.state.cursorCol);
685
+ const after = currentLine.slice(this.state.cursorCol);
686
+ // Split current line
687
+ this.state.lines[this.state.cursorLine] = before;
688
+ this.state.lines.splice(this.state.cursorLine + 1, 0, after);
689
+ // Move cursor to start of new line
690
+ this.state.cursorLine++;
691
+ this.state.cursorCol = 0;
692
+ if (this.onChange) {
693
+ this.onChange(this.getText());
694
+ }
695
+ }
696
+ handleBackspace() {
697
+ if (this.state.cursorCol > 0) {
698
+ // Delete character in current line
699
+ const line = this.state.lines[this.state.cursorLine] || "";
700
+ const before = line.slice(0, this.state.cursorCol - 1);
701
+ const after = line.slice(this.state.cursorCol);
702
+ this.state.lines[this.state.cursorLine] = before + after;
703
+ this.state.cursorCol--;
704
+ }
705
+ else if (this.state.cursorLine > 0) {
706
+ // Merge with previous line
707
+ const currentLine = this.state.lines[this.state.cursorLine] || "";
708
+ const previousLine = this.state.lines[this.state.cursorLine - 1] || "";
709
+ this.state.lines[this.state.cursorLine - 1] = previousLine + currentLine;
710
+ this.state.lines.splice(this.state.cursorLine, 1);
711
+ this.state.cursorLine--;
712
+ this.state.cursorCol = previousLine.length;
713
+ }
714
+ if (this.onChange) {
715
+ this.onChange(this.getText());
716
+ }
717
+ // Update autocomplete after backspace
718
+ if (this.isAutocompleting) {
719
+ void this.updateAutocomplete();
720
+ }
721
+ else {
722
+ // Check if we should trigger autocomplete after backspace in slash command context
723
+ const currentLine = this.state.lines[this.state.cursorLine] || "";
724
+ const textBeforeCursor = currentLine.slice(0, this.state.cursorCol);
725
+ // Trigger autocomplete if we're in a slash command context (typing command name)
726
+ if (textBeforeCursor.startsWith("/") && !textBeforeCursor.includes(" ")) {
727
+ void this.tryTriggerAutocomplete();
728
+ }
729
+ }
730
+ }
731
+ moveToLineStart() {
732
+ this.state.cursorCol = 0;
733
+ }
734
+ moveToLineEnd() {
735
+ const currentLine = this.state.lines[this.state.cursorLine] || "";
736
+ this.state.cursorCol = currentLine.length;
737
+ }
738
+ handleForwardDelete() {
739
+ const currentLine = this.state.lines[this.state.cursorLine] || "";
740
+ if (this.state.cursorCol < currentLine.length) {
741
+ // Delete character at cursor position (forward delete)
742
+ const before = currentLine.slice(0, this.state.cursorCol);
743
+ const after = currentLine.slice(this.state.cursorCol + 1);
744
+ this.state.lines[this.state.cursorLine] = before + after;
745
+ }
746
+ else if (this.state.cursorLine < this.state.lines.length - 1) {
747
+ // At end of line - merge with next line
748
+ const nextLine = this.state.lines[this.state.cursorLine + 1] || "";
749
+ this.state.lines[this.state.cursorLine] = currentLine + nextLine;
750
+ this.state.lines.splice(this.state.cursorLine + 1, 1);
751
+ }
752
+ if (this.onChange) {
753
+ this.onChange(this.getText());
754
+ }
755
+ }
756
+ deleteToStartOfLine() {
757
+ this.historyIndex = -1; // Exit history browsing mode
758
+ const currentLine = this.state.lines[this.state.cursorLine] || "";
759
+ if (this.state.cursorCol > 0) {
760
+ // Delete from start of line up to cursor
761
+ this.state.lines[this.state.cursorLine] = currentLine.slice(this.state.cursorCol);
762
+ this.state.cursorCol = 0;
763
+ }
764
+ else if (this.state.cursorLine > 0) {
765
+ // At start of line - merge with previous line
766
+ const previousLine = this.state.lines[this.state.cursorLine - 1] || "";
767
+ this.state.lines[this.state.cursorLine - 1] = previousLine + currentLine;
768
+ this.state.lines.splice(this.state.cursorLine, 1);
769
+ this.state.cursorLine--;
770
+ this.state.cursorCol = previousLine.length;
771
+ }
772
+ if (this.onChange) {
773
+ this.onChange(this.getText());
774
+ }
775
+ }
776
+ deleteToEndOfLine() {
777
+ this.historyIndex = -1; // Exit history browsing mode
778
+ const currentLine = this.state.lines[this.state.cursorLine] || "";
779
+ if (this.state.cursorCol < currentLine.length) {
780
+ // Delete from cursor to end of line
781
+ this.state.lines[this.state.cursorLine] = currentLine.slice(0, this.state.cursorCol);
782
+ }
783
+ else if (this.state.cursorLine < this.state.lines.length - 1) {
784
+ // At end of line - merge with next line
785
+ const nextLine = this.state.lines[this.state.cursorLine + 1] || "";
786
+ this.state.lines[this.state.cursorLine] = currentLine + nextLine;
787
+ this.state.lines.splice(this.state.cursorLine + 1, 1);
788
+ }
789
+ if (this.onChange) {
790
+ this.onChange(this.getText());
791
+ }
792
+ }
793
+ deleteWordBackwards() {
794
+ this.historyIndex = -1; // Exit history browsing mode
795
+ const currentLine = this.state.lines[this.state.cursorLine] || "";
796
+ // If at start of line, behave like backspace at column 0 (merge with previous line)
797
+ if (this.state.cursorCol === 0) {
798
+ if (this.state.cursorLine > 0) {
799
+ const previousLine = this.state.lines[this.state.cursorLine - 1] || "";
800
+ this.state.lines[this.state.cursorLine - 1] =
801
+ previousLine + currentLine;
802
+ this.state.lines.splice(this.state.cursorLine, 1);
803
+ this.state.cursorLine--;
804
+ this.state.cursorCol = previousLine.length;
805
+ }
806
+ }
807
+ else {
808
+ const textBeforeCursor = currentLine.slice(0, this.state.cursorCol);
809
+ const isWhitespace = (char) => /\s/.test(char);
810
+ const isPunctuation = (char) => {
811
+ // Treat obvious code punctuation as boundaries
812
+ return /[(){}[\]<>.,;:'"!?+\-=*/\\|&%^$#@~`]/.test(char);
813
+ };
814
+ let deleteFrom = this.state.cursorCol;
815
+ const lastChar = textBeforeCursor[deleteFrom - 1] ?? "";
816
+ // If immediately on whitespace or punctuation, delete that single boundary char
817
+ if (isWhitespace(lastChar) || isPunctuation(lastChar)) {
818
+ deleteFrom -= 1;
819
+ }
820
+ else {
821
+ // Otherwise, delete a run of non-boundary characters (the "word")
822
+ while (deleteFrom > 0) {
823
+ const ch = textBeforeCursor[deleteFrom - 1] ?? "";
824
+ if (isWhitespace(ch) || isPunctuation(ch)) {
825
+ break;
826
+ }
827
+ deleteFrom -= 1;
828
+ }
829
+ }
830
+ this.state.lines[this.state.cursorLine] =
831
+ currentLine.slice(0, deleteFrom) +
832
+ currentLine.slice(this.state.cursorCol);
833
+ this.state.cursorCol = deleteFrom;
834
+ }
835
+ if (this.onChange) {
836
+ this.onChange(this.getText());
837
+ }
838
+ }
839
+ /**
840
+ * Build a mapping from visual lines to logical positions.
841
+ * Returns an array where each element represents a visual line with:
842
+ * - logicalLine: index into this.state.lines
843
+ * - startCol: starting column in the logical line
844
+ * - length: length of this visual line segment
845
+ */
846
+ buildVisualLineMap(width) {
847
+ const visualLines = [];
848
+ for (let i = 0; i < this.state.lines.length; i++) {
849
+ const line = this.state.lines[i] || "";
850
+ const metrics = lineMetricsCache.get(line);
851
+ const lineVisWidth = metrics.totalWidth;
852
+ if (line.length === 0) {
853
+ // Empty line still takes one visual line
854
+ visualLines.push({ logicalLine: i, startCol: 0, length: 0 });
855
+ }
856
+ else if (lineVisWidth <= width) {
857
+ visualLines.push({ logicalLine: i, startCol: 0, length: line.length });
858
+ }
859
+ else {
860
+ // Line needs wrapping - use cached graphemes and widths
861
+ let currentWidth = 0;
862
+ let chunkStartIndex = 0;
863
+ let currentIndex = 0;
864
+ for (let g = 0; g < metrics.graphemes.length; g++) {
865
+ const grapheme = metrics.graphemes[g];
866
+ const graphemeWidth = metrics.widths[g];
867
+ if (currentWidth + graphemeWidth > width &&
868
+ currentIndex > chunkStartIndex) {
869
+ // Start a new chunk
870
+ visualLines.push({
871
+ logicalLine: i,
872
+ startCol: chunkStartIndex,
873
+ length: currentIndex - chunkStartIndex,
874
+ });
875
+ chunkStartIndex = currentIndex;
876
+ currentWidth = graphemeWidth;
877
+ }
878
+ else {
879
+ currentWidth += graphemeWidth;
880
+ }
881
+ currentIndex += grapheme.length;
882
+ }
883
+ // Push the last chunk
884
+ if (currentIndex > chunkStartIndex) {
885
+ visualLines.push({
886
+ logicalLine: i,
887
+ startCol: chunkStartIndex,
888
+ length: currentIndex - chunkStartIndex,
889
+ });
890
+ }
891
+ }
892
+ }
893
+ return visualLines;
894
+ }
895
+ /**
896
+ * Find the visual line index for the current cursor position.
897
+ */
898
+ findCurrentVisualLine(visualLines) {
899
+ for (let i = 0; i < visualLines.length; i++) {
900
+ const vl = visualLines[i];
901
+ if (!vl)
902
+ continue;
903
+ if (vl.logicalLine === this.state.cursorLine) {
904
+ const colInSegment = this.state.cursorCol - vl.startCol;
905
+ // Cursor is in this segment if it's within range
906
+ // For the last segment of a logical line, cursor can be at length (end position)
907
+ const isLastSegmentOfLine = i === visualLines.length - 1 ||
908
+ visualLines[i + 1]?.logicalLine !== vl.logicalLine;
909
+ if (colInSegment >= 0 &&
910
+ (colInSegment < vl.length ||
911
+ (isLastSegmentOfLine && colInSegment <= vl.length))) {
912
+ return i;
913
+ }
914
+ }
915
+ }
916
+ // Fallback: return last visual line
917
+ return visualLines.length - 1;
918
+ }
919
+ moveCursor(deltaLine, deltaCol) {
920
+ const width = this.lastWidth;
921
+ if (deltaLine !== 0) {
922
+ // Build visual line map for navigation
923
+ const visualLines = this.buildVisualLineMap(width);
924
+ const currentVisualLine = this.findCurrentVisualLine(visualLines);
925
+ // Calculate column position within current visual line
926
+ const currentVl = visualLines[currentVisualLine];
927
+ const visualCol = currentVl
928
+ ? this.state.cursorCol - currentVl.startCol
929
+ : 0;
930
+ // Move to target visual line
931
+ const targetVisualLine = currentVisualLine + deltaLine;
932
+ if (targetVisualLine >= 0 && targetVisualLine < visualLines.length) {
933
+ const targetVl = visualLines[targetVisualLine];
934
+ if (targetVl) {
935
+ this.state.cursorLine = targetVl.logicalLine;
936
+ // Try to maintain visual column position, clamped to line length
937
+ const targetCol = targetVl.startCol + Math.min(visualCol, targetVl.length);
938
+ const logicalLine = this.state.lines[targetVl.logicalLine] || "";
939
+ this.state.cursorCol = Math.min(targetCol, logicalLine.length);
940
+ }
941
+ }
942
+ }
943
+ if (deltaCol !== 0) {
944
+ const currentLine = this.state.lines[this.state.cursorLine] || "";
945
+ if (deltaCol > 0) {
946
+ // Moving right
947
+ if (this.state.cursorCol < currentLine.length) {
948
+ this.state.cursorCol++;
949
+ }
950
+ else if (this.state.cursorLine < this.state.lines.length - 1) {
951
+ // Wrap to start of next logical line
952
+ this.state.cursorLine++;
953
+ this.state.cursorCol = 0;
954
+ }
955
+ }
956
+ else {
957
+ // Moving left
958
+ if (this.state.cursorCol > 0) {
959
+ this.state.cursorCol--;
960
+ }
961
+ else if (this.state.cursorLine > 0) {
962
+ // Wrap to end of previous logical line
963
+ this.state.cursorLine--;
964
+ const prevLine = this.state.lines[this.state.cursorLine] || "";
965
+ this.state.cursorCol = prevLine.length;
966
+ }
967
+ }
968
+ }
969
+ }
970
+ isWordBoundary(char) {
971
+ return /\s/.test(char) || /[(){}[\]<>.,;:'"!?+\-=*/\\|&%^$#@~`]/.test(char);
972
+ }
973
+ moveWordBackwards() {
974
+ const currentLine = this.state.lines[this.state.cursorLine] || "";
975
+ // If at start of line, move to end of previous line
976
+ if (this.state.cursorCol === 0) {
977
+ if (this.state.cursorLine > 0) {
978
+ this.state.cursorLine--;
979
+ const prevLine = this.state.lines[this.state.cursorLine] || "";
980
+ this.state.cursorCol = prevLine.length;
981
+ }
982
+ return;
983
+ }
984
+ const textBeforeCursor = currentLine.slice(0, this.state.cursorCol);
985
+ let newCol = this.state.cursorCol;
986
+ const lastChar = textBeforeCursor[newCol - 1] ?? "";
987
+ // If immediately on whitespace or punctuation, skip that single boundary char
988
+ if (this.isWordBoundary(lastChar)) {
989
+ newCol -= 1;
990
+ }
991
+ // Now skip the "word" (non-boundary characters)
992
+ while (newCol > 0) {
993
+ const ch = textBeforeCursor[newCol - 1] ?? "";
994
+ if (this.isWordBoundary(ch)) {
995
+ break;
996
+ }
997
+ newCol -= 1;
998
+ }
999
+ this.state.cursorCol = newCol;
1000
+ }
1001
+ moveWordForwards() {
1002
+ const currentLine = this.state.lines[this.state.cursorLine] || "";
1003
+ // If at end of line, move to start of next line
1004
+ if (this.state.cursorCol >= currentLine.length) {
1005
+ if (this.state.cursorLine < this.state.lines.length - 1) {
1006
+ this.state.cursorLine++;
1007
+ this.state.cursorCol = 0;
1008
+ }
1009
+ return;
1010
+ }
1011
+ let newCol = this.state.cursorCol;
1012
+ const charAtCursor = currentLine[newCol] ?? "";
1013
+ // If on whitespace or punctuation, skip it
1014
+ if (this.isWordBoundary(charAtCursor)) {
1015
+ newCol += 1;
1016
+ }
1017
+ // Skip the "word" (non-boundary characters)
1018
+ while (newCol < currentLine.length) {
1019
+ const ch = currentLine[newCol] ?? "";
1020
+ if (this.isWordBoundary(ch)) {
1021
+ break;
1022
+ }
1023
+ newCol += 1;
1024
+ }
1025
+ this.state.cursorCol = newCol;
1026
+ }
1027
+ // Helper method to check if cursor is at start of message (for slash command detection)
1028
+ isAtStartOfMessage() {
1029
+ const currentLine = this.state.lines[this.state.cursorLine] || "";
1030
+ const beforeCursor = currentLine.slice(0, this.state.cursorCol);
1031
+ // At start if line is empty, only contains whitespace, or is just "/"
1032
+ return beforeCursor.trim() === "" || beforeCursor.trim() === "/";
1033
+ }
1034
+ // Autocomplete methods
1035
+ async tryTriggerAutocomplete(explicitTab = false) {
1036
+ if (!this.autocompleteProvider)
1037
+ return;
1038
+ // Check if we should trigger file completion on Tab
1039
+ if (explicitTab) {
1040
+ const provider = this
1041
+ .autocompleteProvider;
1042
+ // Only check file completion triggering if the provider has the method
1043
+ // For slash commands, we always want to show autocomplete
1044
+ if (provider.shouldTriggerFileCompletion &&
1045
+ !provider.shouldTriggerFileCompletion(this.state.lines, this.state.cursorLine, this.state.cursorCol)) {
1046
+ return;
1047
+ }
1048
+ }
1049
+ const suggestions = await this.autocompleteProvider.getSuggestions(this.state.lines, this.state.cursorLine, this.state.cursorCol);
1050
+ if (suggestions && suggestions.items.length > 0) {
1051
+ this.autocompletePrefix = suggestions.prefix;
1052
+ this.isAutocompleting = true;
1053
+ if (this.autocompleteList) {
1054
+ this.autocompleteList.updateItems(suggestions.items);
1055
+ }
1056
+ else {
1057
+ this.autocompleteList = new SelectList(suggestions.items, 5, this.theme.selectList);
1058
+ }
1059
+ // Request re-render to show autocomplete list
1060
+ this.onRenderRequested?.();
1061
+ }
1062
+ else {
1063
+ this.cancelAutocomplete();
1064
+ }
1065
+ }
1066
+ async handleTabCompletion() {
1067
+ if (!this.autocompleteProvider)
1068
+ return;
1069
+ const currentLine = this.state.lines[this.state.cursorLine] || "";
1070
+ const beforeCursor = currentLine.slice(0, this.state.cursorCol);
1071
+ // Check if we're in a slash command context
1072
+ if (beforeCursor.trimStart().startsWith("/")) {
1073
+ await this.handleSlashCommandCompletion();
1074
+ }
1075
+ else {
1076
+ await this.forceFileAutocomplete();
1077
+ }
1078
+ }
1079
+ async handleSlashCommandCompletion() {
1080
+ // For now, fall back to regular autocomplete (slash commands)
1081
+ // This can be extended later to handle command-specific argument completion
1082
+ await this.tryTriggerAutocomplete(true);
1083
+ }
1084
+ async forceFileAutocomplete() {
1085
+ if (!this.autocompleteProvider)
1086
+ return;
1087
+ // Check if provider has the force method
1088
+ const provider = this.autocompleteProvider;
1089
+ if (!provider.getForceFileSuggestions) {
1090
+ await this.tryTriggerAutocomplete(true);
1091
+ return;
1092
+ }
1093
+ const suggestions = await provider.getForceFileSuggestions(this.state.lines, this.state.cursorLine, this.state.cursorCol);
1094
+ if (suggestions && suggestions.items.length > 0) {
1095
+ this.autocompletePrefix = suggestions.prefix;
1096
+ if (this.autocompleteList) {
1097
+ this.autocompleteList.updateItems(suggestions.items);
1098
+ }
1099
+ else {
1100
+ this.autocompleteList = new SelectList(suggestions.items, 5, this.theme.selectList);
1101
+ }
1102
+ this.isAutocompleting = true;
1103
+ // Request re-render to show autocomplete list
1104
+ this.onRenderRequested?.();
1105
+ }
1106
+ else {
1107
+ this.cancelAutocomplete();
1108
+ }
1109
+ }
1110
+ cancelAutocomplete() {
1111
+ this.isAutocompleting = false;
1112
+ this.autocompleteList = undefined;
1113
+ this.autocompletePrefix = "";
1114
+ if (this.autocompleteDebounceTimer) {
1115
+ clearTimeout(this.autocompleteDebounceTimer);
1116
+ this.autocompleteDebounceTimer = undefined;
1117
+ }
1118
+ }
1119
+ isShowingAutocomplete() {
1120
+ return this.isAutocompleting;
1121
+ }
1122
+ async updateAutocomplete() {
1123
+ if (!this.isAutocompleting || !this.autocompleteProvider)
1124
+ return;
1125
+ // Check if the current text still matches our autocomplete context
1126
+ // This prevents unnecessary updates when typing unrelated text
1127
+ const currentLine = this.state.lines[this.state.cursorLine] || "";
1128
+ const textBeforeCursor = currentLine.slice(0, this.state.cursorCol);
1129
+ // If we're no longer in the context that triggered autocomplete, cancel it
1130
+ // For slash commands, check if we're still in slash command context
1131
+ // For file paths, check if we're still in the same path context
1132
+ if (textBeforeCursor.startsWith("/")) {
1133
+ // For slash commands, we should continue autocomplete as long as we're in slash command context
1134
+ // Don't cancel based on prefix matching for progressive typing
1135
+ }
1136
+ else {
1137
+ // For file paths, check if we're still in the same path context
1138
+ if (!textBeforeCursor.endsWith(this.autocompletePrefix)) {
1139
+ this.cancelAutocomplete();
1140
+ return;
1141
+ }
1142
+ }
1143
+ // Clear any existing debounce timer
1144
+ if (this.autocompleteDebounceTimer) {
1145
+ clearTimeout(this.autocompleteDebounceTimer);
1146
+ }
1147
+ // Debounce autocomplete updates to prevent rapid-fire file system operations
1148
+ this.autocompleteDebounceTimer = setTimeout(async () => {
1149
+ const suggestions = await this.autocompleteProvider?.getSuggestions(this.state.lines, this.state.cursorLine, this.state.cursorCol);
1150
+ if (suggestions && suggestions.items.length > 0) {
1151
+ this.autocompletePrefix = suggestions.prefix;
1152
+ if (this.autocompleteList) {
1153
+ // Update the existing list with new items
1154
+ this.autocompleteList.updateItems(suggestions.items);
1155
+ }
1156
+ else {
1157
+ this.autocompleteList = new SelectList(suggestions.items, 5, this.theme.selectList);
1158
+ }
1159
+ this.isAutocompleting = true;
1160
+ // Request re-render to show updated autocomplete list
1161
+ this.onRenderRequested?.();
1162
+ }
1163
+ else {
1164
+ // No more matches, cancel autocomplete
1165
+ this.cancelAutocomplete();
1166
+ // Request re-render to hide autocomplete
1167
+ this.onRenderRequested?.();
1168
+ }
1169
+ }, 50); // 50ms debounce delay
1170
+ }
1171
+ isModifiedEnter(data) {
1172
+ // Common modified Enter sequences across terminals
1173
+ const sequences = [
1174
+ // Shift+Enter sequences
1175
+ "\x1b[13;2~", // Some terminals
1176
+ "\x1bOM", // Some terminals
1177
+ "\\\r", // VS Code terminal
1178
+ "\x1b\r", // Option+Enter (macOS)
1179
+ "\x1b[27;2;13~", // xterm shift+enter
1180
+ "\x1b[13;2u", // libtermkey shift+enter
1181
+ // Ctrl+Enter sequences
1182
+ "\x1b[13;5~", // Some terminals
1183
+ "\x1b[27;5;13~", // xterm ctrl+enter
1184
+ "\x1b[13;5u", // libtermkey ctrl+enter
1185
+ ];
1186
+ // Check for known sequences
1187
+ if (sequences.includes(data)) {
1188
+ return true;
1189
+ }
1190
+ // Check for Enter with escape sequences (general case)
1191
+ if (data.length > 1 &&
1192
+ data.includes("\x1b") &&
1193
+ (data.includes("\r") || data.includes("\n"))) {
1194
+ return true;
1195
+ }
1196
+ // Check for Ctrl+Enter (Ctrl + CR) or Ctrl+Enter with LF
1197
+ if ((data.charCodeAt(0) === 13 || data.charCodeAt(0) === 10) &&
1198
+ data.length > 1) {
1199
+ return true;
1200
+ }
1201
+ return false;
1202
+ }
1203
+ getCursorPosition() {
1204
+ // Return cursor position relative to the editor component
1205
+ // The editor has a top border line, then content lines, then a bottom border line
1206
+ // So cursor position within editor is: row = layoutLineIndex + 1
1207
+ const width = 80; // Use a reasonable default width for calculation
1208
+ const layoutLines = this.layoutText(width);
1209
+ // Find which layout line contains the cursor
1210
+ for (let i = 0; i < layoutLines.length; i++) {
1211
+ const layoutLine = layoutLines[i];
1212
+ if (layoutLine.hasCursor && layoutLine.cursorPos !== undefined) {
1213
+ // Add 1 to account for the top border line
1214
+ return [i + 1, layoutLine.cursorPos];
1215
+ }
1216
+ }
1217
+ // If no cursor found, return position at start of first content line (after top border)
1218
+ return [1, 0];
1219
+ }
1220
+ }