@travisennis/acai 0.0.6 → 0.0.8

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 (586) hide show
  1. package/README.md +225 -39
  2. package/dist/agent/index.d.ts +30 -21
  3. package/dist/agent/index.d.ts.map +1 -1
  4. package/dist/agent/index.js +237 -198
  5. package/dist/cli.d.ts +4 -3
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/cli.js +56 -18
  8. package/dist/commands/add-directory/index.d.ts +3 -0
  9. package/dist/commands/add-directory/index.d.ts.map +1 -0
  10. package/dist/commands/add-directory/index.js +50 -0
  11. package/dist/commands/add-directory/types.d.ts +6 -0
  12. package/dist/commands/add-directory/types.d.ts.map +1 -0
  13. package/dist/commands/add-directory/utils.d.ts +3 -0
  14. package/dist/commands/add-directory/utils.d.ts.map +1 -0
  15. package/dist/commands/add-directory/utils.js +15 -0
  16. package/dist/commands/clear/index.d.ts +3 -0
  17. package/dist/commands/clear/index.d.ts.map +1 -0
  18. package/dist/commands/{clear-command.js → clear/index.js} +1 -7
  19. package/dist/commands/copy/index.d.ts +3 -0
  20. package/dist/commands/copy/index.d.ts.map +1 -0
  21. package/dist/commands/copy/index.js +39 -0
  22. package/dist/commands/copy/types.d.ts +3 -0
  23. package/dist/commands/copy/types.d.ts.map +1 -0
  24. package/dist/commands/copy/types.js +1 -0
  25. package/dist/commands/copy/utils.d.ts +3 -0
  26. package/dist/commands/copy/utils.d.ts.map +1 -0
  27. package/dist/commands/copy/utils.js +22 -0
  28. package/dist/commands/exit/index.d.ts +10 -0
  29. package/dist/commands/exit/index.d.ts.map +1 -0
  30. package/dist/commands/exit/index.js +21 -0
  31. package/dist/commands/exit/types.d.ts +8 -0
  32. package/dist/commands/exit/types.d.ts.map +1 -0
  33. package/dist/commands/exit/types.js +1 -0
  34. package/dist/commands/exit/utils.d.ts +2 -0
  35. package/dist/commands/exit/utils.d.ts.map +1 -0
  36. package/dist/commands/exit/utils.js +13 -0
  37. package/dist/commands/generate-rules/index.d.ts +3 -0
  38. package/dist/commands/generate-rules/index.d.ts.map +1 -0
  39. package/dist/commands/{generate-rules-command.js → generate-rules/index.js} +65 -147
  40. package/dist/commands/generate-rules/utils.d.ts +5 -0
  41. package/dist/commands/generate-rules/utils.d.ts.map +1 -0
  42. package/dist/commands/generate-rules/utils.js +25 -0
  43. package/dist/commands/handoff/index.d.ts +3 -0
  44. package/dist/commands/handoff/index.d.ts.map +1 -0
  45. package/dist/commands/handoff/index.js +97 -0
  46. package/dist/commands/handoff/utils.d.ts +4 -0
  47. package/dist/commands/handoff/utils.d.ts.map +1 -0
  48. package/dist/commands/handoff/utils.js +123 -0
  49. package/dist/commands/health/index.d.ts +3 -0
  50. package/dist/commands/health/index.d.ts.map +1 -0
  51. package/dist/commands/health/index.js +56 -0
  52. package/dist/commands/health/utils.d.ts +15 -0
  53. package/dist/commands/health/utils.d.ts.map +1 -0
  54. package/dist/commands/health/utils.js +52 -0
  55. package/dist/commands/help/index.d.ts +3 -0
  56. package/dist/commands/help/index.d.ts.map +1 -0
  57. package/dist/commands/{help-command.js → help/index.js} +6 -14
  58. package/dist/commands/history/index.d.ts +3 -0
  59. package/dist/commands/history/index.d.ts.map +1 -0
  60. package/dist/commands/{history-command.js → history/index.js} +40 -200
  61. package/dist/commands/history/types.d.ts +11 -0
  62. package/dist/commands/history/types.d.ts.map +1 -0
  63. package/dist/commands/history/types.js +1 -0
  64. package/dist/commands/history/utils.d.ts +4 -0
  65. package/dist/commands/history/utils.d.ts.map +1 -0
  66. package/dist/commands/history/utils.js +86 -0
  67. package/dist/commands/init/index.d.ts +3 -0
  68. package/dist/commands/init/index.d.ts.map +1 -0
  69. package/dist/commands/{init-command.js → init/index.js} +17 -27
  70. package/dist/commands/init-project/index.d.ts +3 -0
  71. package/dist/commands/init-project/index.d.ts.map +1 -0
  72. package/dist/commands/init-project/index.js +51 -0
  73. package/dist/commands/init-project/utils.d.ts +9 -0
  74. package/dist/commands/init-project/utils.d.ts.map +1 -0
  75. package/dist/commands/init-project/utils.js +43 -0
  76. package/dist/commands/list-directories/index.d.ts +3 -0
  77. package/dist/commands/list-directories/index.d.ts.map +1 -0
  78. package/dist/commands/{list-directories-command.js → list-directories/index.js} +2 -15
  79. package/dist/commands/list-tools/index.d.ts +3 -0
  80. package/dist/commands/list-tools/index.d.ts.map +1 -0
  81. package/dist/commands/list-tools/index.js +89 -0
  82. package/dist/commands/manager.d.ts +2 -9
  83. package/dist/commands/manager.d.ts.map +1 -1
  84. package/dist/commands/manager.js +38 -83
  85. package/dist/commands/model/index.d.ts +3 -0
  86. package/dist/commands/model/index.d.ts.map +1 -0
  87. package/dist/commands/model/index.js +182 -0
  88. package/dist/commands/model/utils.d.ts +3 -0
  89. package/dist/commands/model/utils.d.ts.map +1 -0
  90. package/dist/commands/model/utils.js +5 -0
  91. package/dist/commands/paste/index.d.ts +3 -0
  92. package/dist/commands/paste/index.d.ts.map +1 -0
  93. package/dist/commands/paste/index.js +94 -0
  94. package/dist/commands/paste/utils.d.ts +5 -0
  95. package/dist/commands/paste/utils.d.ts.map +1 -0
  96. package/dist/commands/paste/utils.js +86 -0
  97. package/dist/commands/pickup/index.d.ts +3 -0
  98. package/dist/commands/pickup/index.d.ts.map +1 -0
  99. package/dist/commands/pickup/index.js +138 -0
  100. package/dist/commands/pickup/types.d.ts +6 -0
  101. package/dist/commands/pickup/types.d.ts.map +1 -0
  102. package/dist/commands/pickup/types.js +1 -0
  103. package/dist/commands/pickup/utils.d.ts +7 -0
  104. package/dist/commands/pickup/utils.d.ts.map +1 -0
  105. package/dist/commands/pickup/utils.js +56 -0
  106. package/dist/commands/prompt/index.d.ts +5 -0
  107. package/dist/commands/prompt/index.d.ts.map +1 -0
  108. package/dist/commands/prompt/index.js +126 -0
  109. package/dist/commands/prompt/types.d.ts +15 -0
  110. package/dist/commands/prompt/types.d.ts.map +1 -0
  111. package/dist/commands/prompt/types.js +1 -0
  112. package/dist/commands/prompt/utils.d.ts +12 -0
  113. package/dist/commands/prompt/utils.d.ts.map +1 -0
  114. package/dist/commands/prompt/utils.js +107 -0
  115. package/dist/commands/remove-directory/index.d.ts +3 -0
  116. package/dist/commands/remove-directory/index.d.ts.map +1 -0
  117. package/dist/commands/{remove-directory-command.js → remove-directory/index.js} +3 -35
  118. package/dist/commands/reset/index.d.ts +3 -0
  119. package/dist/commands/reset/index.d.ts.map +1 -0
  120. package/dist/commands/{reset-command.js → reset/index.js} +8 -11
  121. package/dist/commands/reset/types.d.ts +1 -0
  122. package/dist/commands/reset/types.d.ts.map +1 -0
  123. package/dist/commands/reset/types.js +3 -0
  124. package/dist/commands/resources/index.d.ts +3 -0
  125. package/dist/commands/resources/index.d.ts.map +1 -0
  126. package/dist/commands/resources/index.js +84 -0
  127. package/dist/commands/review/index.d.ts +3 -0
  128. package/dist/commands/review/index.d.ts.map +1 -0
  129. package/dist/commands/review/index.js +126 -0
  130. package/dist/commands/review/types.d.ts +12 -0
  131. package/dist/commands/review/types.d.ts.map +1 -0
  132. package/dist/commands/review/types.js +1 -0
  133. package/dist/commands/review/utils.d.ts +4 -0
  134. package/dist/commands/review/utils.d.ts.map +1 -0
  135. package/dist/commands/review/utils.js +87 -0
  136. package/dist/commands/save/index.d.ts +3 -0
  137. package/dist/commands/save/index.d.ts.map +1 -0
  138. package/dist/commands/{save-command.js → save/index.js} +3 -10
  139. package/dist/commands/session/index.d.ts +3 -0
  140. package/dist/commands/session/index.d.ts.map +1 -0
  141. package/dist/commands/session/index.js +197 -0
  142. package/dist/commands/session/types.d.ts +13 -0
  143. package/dist/commands/session/types.d.ts.map +1 -0
  144. package/dist/commands/session/types.js +7 -0
  145. package/dist/commands/shell/index.d.ts +3 -0
  146. package/dist/commands/shell/index.d.ts.map +1 -0
  147. package/dist/commands/{shell-command.js → shell/index.js} +4 -51
  148. package/dist/commands/types.d.ts +2 -5
  149. package/dist/commands/types.d.ts.map +1 -1
  150. package/dist/config.d.ts +36 -7
  151. package/dist/config.d.ts.map +1 -1
  152. package/dist/config.js +118 -60
  153. package/dist/dedent.d.ts.map +1 -1
  154. package/dist/dedent.js +7 -7
  155. package/dist/execution/index.d.ts +18 -2
  156. package/dist/execution/index.d.ts.map +1 -1
  157. package/dist/execution/index.js +119 -81
  158. package/dist/formatting.d.ts +46 -0
  159. package/dist/formatting.d.ts.map +1 -1
  160. package/dist/formatting.js +94 -0
  161. package/dist/index.d.ts +1 -1
  162. package/dist/index.d.ts.map +1 -1
  163. package/dist/index.js +299 -177
  164. package/dist/logger.d.ts.map +1 -1
  165. package/dist/logger.js +4 -11
  166. package/dist/mentions.d.ts.map +1 -1
  167. package/dist/mentions.js +3 -53
  168. package/dist/middleware/audit-message.d.ts +2 -2
  169. package/dist/middleware/audit-message.d.ts.map +1 -1
  170. package/dist/middleware/audit-message.js +40 -2
  171. package/dist/middleware/cache.d.ts +2 -2
  172. package/dist/middleware/cache.d.ts.map +1 -1
  173. package/dist/middleware/cache.js +111 -27
  174. package/dist/middleware/rate-limit.d.ts +2 -2
  175. package/dist/middleware/rate-limit.d.ts.map +1 -1
  176. package/dist/middleware/rate-limit.js +1 -0
  177. package/dist/models/ai-config.d.ts.map +1 -1
  178. package/dist/models/ai-config.js +46 -29
  179. package/dist/models/anthropic-provider.d.ts +14 -13
  180. package/dist/models/anthropic-provider.d.ts.map +1 -1
  181. package/dist/models/anthropic-provider.js +0 -7
  182. package/dist/models/deepseek-provider.d.ts +9 -8
  183. package/dist/models/deepseek-provider.d.ts.map +1 -1
  184. package/dist/models/deepseek-provider.js +0 -2
  185. package/dist/models/google-provider.d.ts +10 -9
  186. package/dist/models/google-provider.d.ts.map +1 -1
  187. package/dist/models/google-provider.js +0 -3
  188. package/dist/models/groq-provider.d.ts +8 -7
  189. package/dist/models/groq-provider.d.ts.map +1 -1
  190. package/dist/models/groq-provider.js +0 -1
  191. package/dist/models/manager.d.ts +7 -4
  192. package/dist/models/manager.d.ts.map +1 -1
  193. package/dist/models/manager.js +5 -25
  194. package/dist/models/openai-provider.d.ts +11 -10
  195. package/dist/models/openai-provider.d.ts.map +1 -1
  196. package/dist/models/openai-provider.js +0 -4
  197. package/dist/models/opencode-zen-provider.d.ts +23 -0
  198. package/dist/models/opencode-zen-provider.d.ts.map +1 -0
  199. package/dist/models/opencode-zen-provider.js +76 -0
  200. package/dist/models/openrouter-provider.d.ts +34 -28
  201. package/dist/models/openrouter-provider.d.ts.map +1 -1
  202. package/dist/models/openrouter-provider.js +148 -139
  203. package/dist/models/providers.d.ts +6 -16
  204. package/dist/models/providers.d.ts.map +1 -1
  205. package/dist/models/providers.js +8 -58
  206. package/dist/models/xai-provider.d.ts +9 -8
  207. package/dist/models/xai-provider.d.ts.map +1 -1
  208. package/dist/models/xai-provider.js +0 -2
  209. package/dist/prompts/manager.d.ts +1 -1
  210. package/dist/prompts/manager.d.ts.map +1 -1
  211. package/dist/prompts/manager.js +1 -1
  212. package/dist/prompts.d.ts +8 -4
  213. package/dist/prompts.d.ts.map +1 -1
  214. package/dist/prompts.js +155 -177
  215. package/dist/repl/project-status.d.ts +19 -0
  216. package/dist/repl/project-status.d.ts.map +1 -0
  217. package/dist/repl/project-status.js +78 -0
  218. package/dist/repl-new.d.ts +15 -6
  219. package/dist/repl-new.d.ts.map +1 -1
  220. package/dist/repl-new.js +266 -83
  221. package/dist/{messages.d.ts → sessions/manager.d.ts} +13 -4
  222. package/dist/sessions/manager.d.ts.map +1 -0
  223. package/dist/{messages.js → sessions/manager.js} +126 -16
  224. package/dist/skills.d.ts +16 -0
  225. package/dist/skills.d.ts.map +1 -0
  226. package/dist/skills.js +233 -0
  227. package/dist/terminal/control.d.ts +56 -0
  228. package/dist/terminal/control.d.ts.map +1 -0
  229. package/dist/terminal/control.js +111 -0
  230. package/dist/terminal/default-theme.d.ts +1 -1
  231. package/dist/terminal/default-theme.d.ts.map +1 -1
  232. package/dist/terminal/default-theme.js +24 -28
  233. package/dist/terminal/formatting.d.ts +23 -26
  234. package/dist/terminal/formatting.d.ts.map +1 -1
  235. package/dist/terminal/formatting.js +35 -53
  236. package/dist/terminal/highlight/index.d.ts.map +1 -1
  237. package/dist/terminal/highlight/index.js +3 -6
  238. package/dist/terminal/highlight/theme.d.ts.map +1 -1
  239. package/dist/terminal/highlight/theme.js +2 -6
  240. package/dist/terminal/index.d.ts +2 -101
  241. package/dist/terminal/index.d.ts.map +1 -1
  242. package/dist/terminal/index.js +2 -464
  243. package/dist/terminal/keys.d.ts +211 -0
  244. package/dist/terminal/keys.d.ts.map +1 -0
  245. package/dist/terminal/keys.js +546 -0
  246. package/dist/terminal/segmenter.d.ts +6 -0
  247. package/dist/terminal/segmenter.d.ts.map +1 -0
  248. package/dist/terminal/segmenter.js +11 -0
  249. package/dist/terminal/select-prompt.d.ts.map +1 -1
  250. package/dist/terminal/select-prompt.js +9 -21
  251. package/dist/terminal/string-width.d.ts.map +1 -1
  252. package/dist/terminal/string-width.js +40 -21
  253. package/dist/terminal/strip-ansi.d.ts.map +1 -1
  254. package/dist/terminal/strip-ansi.js +9 -15
  255. package/dist/terminal/table/cell.d.ts +114 -0
  256. package/dist/terminal/table/cell.d.ts.map +1 -0
  257. package/dist/terminal/table/cell.js +407 -0
  258. package/dist/terminal/table/debug.d.ts +15 -0
  259. package/dist/terminal/table/debug.d.ts.map +1 -0
  260. package/dist/terminal/table/debug.js +32 -0
  261. package/dist/terminal/table/index.d.ts +3 -0
  262. package/dist/terminal/table/index.d.ts.map +1 -0
  263. package/dist/terminal/table/index.js +2 -0
  264. package/dist/terminal/table/layout-manager.d.ts +27 -0
  265. package/dist/terminal/table/layout-manager.d.ts.map +1 -0
  266. package/dist/terminal/table/layout-manager.js +257 -0
  267. package/dist/terminal/table/table.d.ts +9 -0
  268. package/dist/terminal/table/table.d.ts.map +1 -0
  269. package/dist/terminal/table/table.js +97 -0
  270. package/dist/terminal/table/utils.d.ts +63 -0
  271. package/dist/terminal/table/utils.d.ts.map +1 -0
  272. package/dist/terminal/table/utils.js +326 -0
  273. package/dist/tokens/tracker.d.ts.map +1 -1
  274. package/dist/tokens/tracker.js +58 -16
  275. package/dist/tools/bash.d.ts +11 -11
  276. package/dist/tools/bash.d.ts.map +1 -1
  277. package/dist/tools/bash.js +189 -81
  278. package/dist/tools/directory-tree.d.ts +9 -13
  279. package/dist/tools/directory-tree.d.ts.map +1 -1
  280. package/dist/tools/directory-tree.js +90 -60
  281. package/dist/tools/dynamic-tool-loader.d.ts +22 -8
  282. package/dist/tools/dynamic-tool-loader.d.ts.map +1 -1
  283. package/dist/tools/dynamic-tool-loader.js +41 -46
  284. package/dist/tools/edit-file.d.ts +6 -16
  285. package/dist/tools/edit-file.d.ts.map +1 -1
  286. package/dist/tools/edit-file.js +22 -66
  287. package/dist/tools/glob.d.ts +15 -16
  288. package/dist/tools/glob.d.ts.map +1 -1
  289. package/dist/tools/glob.js +78 -109
  290. package/dist/tools/grep.d.ts +19 -22
  291. package/dist/tools/grep.d.ts.map +1 -1
  292. package/dist/tools/grep.js +61 -93
  293. package/dist/tools/index.d.ts +202 -167
  294. package/dist/tools/index.d.ts.map +1 -1
  295. package/dist/tools/index.js +17 -273
  296. package/dist/tools/ls.d.ts +26 -0
  297. package/dist/tools/ls.d.ts.map +1 -0
  298. package/dist/tools/ls.js +80 -0
  299. package/dist/tools/read-file.d.ts +15 -15
  300. package/dist/tools/read-file.d.ts.map +1 -1
  301. package/dist/tools/read-file.js +52 -76
  302. package/dist/tools/save-file.d.ts +8 -8
  303. package/dist/tools/save-file.d.ts.map +1 -1
  304. package/dist/tools/save-file.js +42 -53
  305. package/dist/tools/think.d.ts +4 -4
  306. package/dist/tools/think.d.ts.map +1 -1
  307. package/dist/tools/think.js +9 -32
  308. package/dist/tools/types.d.ts +5 -21
  309. package/dist/tools/types.d.ts.map +1 -1
  310. package/dist/tools/types.js +0 -9
  311. package/dist/tui/autocomplete/attachment-provider.d.ts +18 -0
  312. package/dist/tui/autocomplete/attachment-provider.d.ts.map +1 -0
  313. package/dist/tui/autocomplete/attachment-provider.js +159 -0
  314. package/dist/tui/autocomplete/base-provider.d.ts +17 -0
  315. package/dist/tui/autocomplete/base-provider.d.ts.map +1 -0
  316. package/dist/tui/autocomplete/base-provider.js +1 -0
  317. package/dist/tui/autocomplete/combined-provider.d.ts +20 -0
  318. package/dist/tui/autocomplete/combined-provider.d.ts.map +1 -0
  319. package/dist/tui/autocomplete/combined-provider.js +61 -0
  320. package/dist/tui/autocomplete/command-provider.d.ts +20 -0
  321. package/dist/tui/autocomplete/command-provider.d.ts.map +1 -0
  322. package/dist/tui/autocomplete/command-provider.js +90 -0
  323. package/dist/tui/autocomplete/file-search-provider.d.ts +16 -0
  324. package/dist/tui/autocomplete/file-search-provider.d.ts.map +1 -0
  325. package/dist/tui/autocomplete/file-search-provider.js +123 -0
  326. package/dist/tui/autocomplete/path-provider.d.ts +21 -0
  327. package/dist/tui/autocomplete/path-provider.d.ts.map +1 -0
  328. package/dist/tui/autocomplete/path-provider.js +164 -0
  329. package/dist/tui/autocomplete/utils.d.ts +16 -0
  330. package/dist/tui/autocomplete/utils.d.ts.map +1 -0
  331. package/dist/tui/autocomplete/utils.js +137 -0
  332. package/dist/tui/autocomplete.d.ts +12 -43
  333. package/dist/tui/autocomplete.d.ts.map +1 -1
  334. package/dist/tui/autocomplete.js +20 -465
  335. package/dist/tui/components/assistant-message.js +1 -1
  336. package/dist/tui/components/box.d.ts +20 -0
  337. package/dist/tui/components/box.d.ts.map +1 -0
  338. package/dist/tui/components/box.js +87 -0
  339. package/dist/tui/components/editor.d.ts +61 -7
  340. package/dist/tui/components/editor.d.ts.map +1 -1
  341. package/dist/tui/components/editor.js +630 -127
  342. package/dist/tui/components/footer.d.ts +19 -18
  343. package/dist/tui/components/footer.d.ts.map +1 -1
  344. package/dist/tui/components/footer.js +90 -185
  345. package/dist/tui/components/header.d.ts +21 -0
  346. package/dist/tui/components/header.d.ts.map +1 -0
  347. package/dist/tui/components/header.js +63 -0
  348. package/dist/tui/components/input.d.ts.map +1 -1
  349. package/dist/tui/components/input.js +8 -7
  350. package/dist/tui/components/loader.d.ts +6 -1
  351. package/dist/tui/components/loader.d.ts.map +1 -1
  352. package/dist/tui/components/loader.js +8 -3
  353. package/dist/tui/components/markdown.d.ts +31 -27
  354. package/dist/tui/components/markdown.d.ts.map +1 -1
  355. package/dist/tui/components/markdown.js +131 -67
  356. package/dist/tui/components/modal.d.ts +0 -11
  357. package/dist/tui/components/modal.d.ts.map +1 -1
  358. package/dist/tui/components/modal.js +9 -37
  359. package/dist/tui/components/notification.d.ts +28 -0
  360. package/dist/tui/components/notification.d.ts.map +1 -0
  361. package/dist/tui/components/notification.js +63 -0
  362. package/dist/tui/components/progress-bar.d.ts +19 -0
  363. package/dist/tui/components/progress-bar.d.ts.map +1 -0
  364. package/dist/tui/components/progress-bar.js +66 -0
  365. package/dist/tui/components/select-list.d.ts +12 -1
  366. package/dist/tui/components/select-list.d.ts.map +1 -1
  367. package/dist/tui/components/select-list.js +73 -32
  368. package/dist/tui/components/spacer.d.ts +1 -1
  369. package/dist/tui/components/spacer.d.ts.map +1 -1
  370. package/dist/tui/components/spacer.js +2 -2
  371. package/dist/tui/components/table.d.ts +27 -0
  372. package/dist/tui/components/table.d.ts.map +1 -0
  373. package/dist/tui/components/table.js +125 -0
  374. package/dist/tui/components/thinking-block.d.ts.map +1 -1
  375. package/dist/tui/components/thinking-block.js +4 -1
  376. package/dist/tui/components/tool-execution.d.ts +7 -6
  377. package/dist/tui/components/tool-execution.d.ts.map +1 -1
  378. package/dist/tui/components/tool-execution.js +81 -85
  379. package/dist/tui/components/user-message.d.ts.map +1 -1
  380. package/dist/tui/components/user-message.js +6 -4
  381. package/dist/tui/components/welcome.d.ts +8 -1
  382. package/dist/tui/components/welcome.d.ts.map +1 -1
  383. package/dist/tui/components/welcome.js +45 -6
  384. package/dist/tui/index.d.ts +12 -6
  385. package/dist/tui/index.d.ts.map +1 -1
  386. package/dist/tui/index.js +7 -3
  387. package/dist/tui/terminal.d.ts +4 -3
  388. package/dist/tui/terminal.d.ts.map +1 -1
  389. package/dist/tui/terminal.js +43 -49
  390. package/dist/tui/tui.d.ts +3 -0
  391. package/dist/tui/tui.d.ts.map +1 -1
  392. package/dist/tui/tui.js +58 -43
  393. package/dist/tui/utils.d.ts +5 -0
  394. package/dist/tui/utils.d.ts.map +1 -1
  395. package/dist/tui/utils.js +80 -1
  396. package/dist/{tools/bash-utils.d.ts → utils/bash.d.ts} +3 -3
  397. package/dist/utils/bash.d.ts.map +1 -0
  398. package/dist/{tools/bash-utils.js → utils/bash.js} +22 -11
  399. package/dist/utils/{filesystem.d.ts → filesystem/operations.d.ts} +1 -1
  400. package/dist/utils/filesystem/operations.d.ts.map +1 -0
  401. package/dist/{tools/filesystem-utils.d.ts → utils/filesystem/security.d.ts} +3 -2
  402. package/dist/utils/filesystem/security.d.ts.map +1 -0
  403. package/dist/{tools/filesystem-utils.js → utils/filesystem/security.js} +62 -4
  404. package/dist/utils/funcs.d.ts +6 -0
  405. package/dist/utils/funcs.d.ts.map +1 -0
  406. package/dist/utils/funcs.js +6 -0
  407. package/dist/{tools/git-utils.d.ts → utils/git.d.ts} +1 -1
  408. package/dist/utils/git.d.ts.map +1 -0
  409. package/dist/{tools/git-utils.js → utils/git.js} +0 -6
  410. package/dist/utils/glob.js +1 -1
  411. package/dist/utils/yaml.d.ts +11 -0
  412. package/dist/utils/yaml.d.ts.map +1 -0
  413. package/dist/utils/yaml.js +207 -0
  414. package/dist/utils/{zod-utils.d.ts → zod.d.ts} +2 -1
  415. package/dist/utils/zod.d.ts.map +1 -0
  416. package/dist/utils/zod.js +24 -0
  417. package/package.json +31 -29
  418. package/dist/agent/manual-loop.d.ts +0 -41
  419. package/dist/agent/manual-loop.d.ts.map +0 -1
  420. package/dist/agent/manual-loop.js +0 -278
  421. package/dist/api/exa/index.d.ts +0 -177
  422. package/dist/api/exa/index.d.ts.map +0 -1
  423. package/dist/api/exa/index.js +0 -439
  424. package/dist/commands/add-directory-command.d.ts +0 -3
  425. package/dist/commands/add-directory-command.d.ts.map +0 -1
  426. package/dist/commands/add-directory-command.js +0 -85
  427. package/dist/commands/application-log-command.d.ts +0 -3
  428. package/dist/commands/application-log-command.d.ts.map +0 -1
  429. package/dist/commands/application-log-command.js +0 -79
  430. package/dist/commands/clear-command.d.ts +0 -3
  431. package/dist/commands/clear-command.d.ts.map +0 -1
  432. package/dist/commands/compact-command.d.ts +0 -3
  433. package/dist/commands/compact-command.d.ts.map +0 -1
  434. package/dist/commands/compact-command.js +0 -64
  435. package/dist/commands/context-command.d.ts +0 -3
  436. package/dist/commands/context-command.d.ts.map +0 -1
  437. package/dist/commands/context-command.js +0 -183
  438. package/dist/commands/copy-command.d.ts +0 -3
  439. package/dist/commands/copy-command.d.ts.map +0 -1
  440. package/dist/commands/copy-command.js +0 -80
  441. package/dist/commands/edit-command.d.ts +0 -3
  442. package/dist/commands/edit-command.d.ts.map +0 -1
  443. package/dist/commands/edit-command.js +0 -88
  444. package/dist/commands/edit-prompt-command.d.ts +0 -3
  445. package/dist/commands/edit-prompt-command.d.ts.map +0 -1
  446. package/dist/commands/edit-prompt-command.js +0 -61
  447. package/dist/commands/exit-command.d.ts +0 -13
  448. package/dist/commands/exit-command.d.ts.map +0 -1
  449. package/dist/commands/exit-command.js +0 -46
  450. package/dist/commands/files-command.d.ts +0 -3
  451. package/dist/commands/files-command.d.ts.map +0 -1
  452. package/dist/commands/files-command.js +0 -121
  453. package/dist/commands/generate-rules-command.d.ts +0 -3
  454. package/dist/commands/generate-rules-command.d.ts.map +0 -1
  455. package/dist/commands/handoff-command.d.ts +0 -3
  456. package/dist/commands/handoff-command.d.ts.map +0 -1
  457. package/dist/commands/handoff-command.js +0 -202
  458. package/dist/commands/health-command.d.ts +0 -4
  459. package/dist/commands/health-command.d.ts.map +0 -1
  460. package/dist/commands/health-command.js +0 -213
  461. package/dist/commands/help-command.d.ts +0 -3
  462. package/dist/commands/help-command.d.ts.map +0 -1
  463. package/dist/commands/history-command.d.ts +0 -3
  464. package/dist/commands/history-command.d.ts.map +0 -1
  465. package/dist/commands/init-command.d.ts +0 -3
  466. package/dist/commands/init-command.d.ts.map +0 -1
  467. package/dist/commands/last-log-command.d.ts +0 -3
  468. package/dist/commands/last-log-command.d.ts.map +0 -1
  469. package/dist/commands/last-log-command.js +0 -98
  470. package/dist/commands/list-directories-command.d.ts +0 -3
  471. package/dist/commands/list-directories-command.d.ts.map +0 -1
  472. package/dist/commands/list-tools-command.d.ts +0 -3
  473. package/dist/commands/list-tools-command.d.ts.map +0 -1
  474. package/dist/commands/list-tools-command.js +0 -124
  475. package/dist/commands/model-command.d.ts +0 -25
  476. package/dist/commands/model-command.d.ts.map +0 -1
  477. package/dist/commands/model-command.js +0 -340
  478. package/dist/commands/paste-command.d.ts +0 -3
  479. package/dist/commands/paste-command.d.ts.map +0 -1
  480. package/dist/commands/paste-command.js +0 -277
  481. package/dist/commands/pickup-command.d.ts +0 -3
  482. package/dist/commands/pickup-command.d.ts.map +0 -1
  483. package/dist/commands/pickup-command.js +0 -161
  484. package/dist/commands/prompt-command.d.ts +0 -3
  485. package/dist/commands/prompt-command.d.ts.map +0 -1
  486. package/dist/commands/prompt-command.js +0 -280
  487. package/dist/commands/remove-directory-command.d.ts +0 -3
  488. package/dist/commands/remove-directory-command.d.ts.map +0 -1
  489. package/dist/commands/reset-command.d.ts +0 -3
  490. package/dist/commands/reset-command.d.ts.map +0 -1
  491. package/dist/commands/rules-command.d.ts +0 -3
  492. package/dist/commands/rules-command.d.ts.map +0 -1
  493. package/dist/commands/rules-command.js +0 -135
  494. package/dist/commands/save-command.d.ts +0 -3
  495. package/dist/commands/save-command.d.ts.map +0 -1
  496. package/dist/commands/shell-command.d.ts +0 -3
  497. package/dist/commands/shell-command.d.ts.map +0 -1
  498. package/dist/commands/usage-command.d.ts +0 -3
  499. package/dist/commands/usage-command.d.ts.map +0 -1
  500. package/dist/commands/usage-command.js +0 -42
  501. package/dist/messages.d.ts.map +0 -1
  502. package/dist/repl/display-tool-messages.d.ts +0 -4
  503. package/dist/repl/display-tool-messages.d.ts.map +0 -1
  504. package/dist/repl/display-tool-messages.js +0 -58
  505. package/dist/repl/display-tool-use.d.ts +0 -14
  506. package/dist/repl/display-tool-use.d.ts.map +0 -1
  507. package/dist/repl/display-tool-use.js +0 -63
  508. package/dist/repl/get-prompt-header.d.ts +0 -8
  509. package/dist/repl/get-prompt-header.d.ts.map +0 -1
  510. package/dist/repl/get-prompt-header.js +0 -9
  511. package/dist/repl/project-status-line.d.ts +0 -2
  512. package/dist/repl/project-status-line.d.ts.map +0 -1
  513. package/dist/repl/project-status-line.js +0 -31
  514. package/dist/repl/prompt.d.ts +0 -21
  515. package/dist/repl/prompt.d.ts.map +0 -1
  516. package/dist/repl/prompt.js +0 -244
  517. package/dist/repl/tool-call-repair.d.ts +0 -4
  518. package/dist/repl/tool-call-repair.d.ts.map +0 -1
  519. package/dist/repl/tool-call-repair.js +0 -54
  520. package/dist/repl.d.ts +0 -29
  521. package/dist/repl.d.ts.map +0 -1
  522. package/dist/repl.js +0 -218
  523. package/dist/terminal/checkbox-prompt.d.ts +0 -36
  524. package/dist/terminal/checkbox-prompt.d.ts.map +0 -1
  525. package/dist/terminal/checkbox-prompt.js +0 -368
  526. package/dist/terminal/editor-prompt.d.ts +0 -10
  527. package/dist/terminal/editor-prompt.d.ts.map +0 -1
  528. package/dist/terminal/editor-prompt.js +0 -61
  529. package/dist/terminal/errors.d.ts +0 -19
  530. package/dist/terminal/errors.d.ts.map +0 -1
  531. package/dist/terminal/errors.js +0 -37
  532. package/dist/terminal/input-prompt.d.ts +0 -17
  533. package/dist/terminal/input-prompt.d.ts.map +0 -1
  534. package/dist/terminal/input-prompt.js +0 -181
  535. package/dist/terminal/markdown.d.ts +0 -2
  536. package/dist/terminal/markdown.d.ts.map +0 -1
  537. package/dist/terminal/markdown.js +0 -118
  538. package/dist/terminal/search-prompt.d.ts +0 -20
  539. package/dist/terminal/search-prompt.d.ts.map +0 -1
  540. package/dist/terminal/search-prompt.js +0 -280
  541. package/dist/terminal/types.d.ts +0 -35
  542. package/dist/terminal/types.d.ts.map +0 -1
  543. package/dist/tokens/threshold.d.ts +0 -35
  544. package/dist/tokens/threshold.d.ts.map +0 -1
  545. package/dist/tokens/threshold.js +0 -85
  546. package/dist/tools/advanced-edit-file.d.ts +0 -69
  547. package/dist/tools/advanced-edit-file.d.ts.map +0 -1
  548. package/dist/tools/advanced-edit-file.js +0 -281
  549. package/dist/tools/agent.d.ts +0 -29
  550. package/dist/tools/agent.d.ts.map +0 -1
  551. package/dist/tools/agent.js +0 -103
  552. package/dist/tools/bash-utils.d.ts.map +0 -1
  553. package/dist/tools/code-interpreter.d.ts +0 -25
  554. package/dist/tools/code-interpreter.d.ts.map +0 -1
  555. package/dist/tools/code-interpreter.js +0 -167
  556. package/dist/tools/delete-file.d.ts +0 -24
  557. package/dist/tools/delete-file.d.ts.map +0 -1
  558. package/dist/tools/delete-file.js +0 -70
  559. package/dist/tools/dynamic-tool-parser.d.ts +0 -21
  560. package/dist/tools/dynamic-tool-parser.d.ts.map +0 -1
  561. package/dist/tools/dynamic-tool-parser.js +0 -22
  562. package/dist/tools/filesystem-utils.d.ts.map +0 -1
  563. package/dist/tools/git-utils.d.ts.map +0 -1
  564. package/dist/tools/llm-edit-fixer.d.ts +0 -25
  565. package/dist/tools/llm-edit-fixer.d.ts.map +0 -1
  566. package/dist/tools/llm-edit-fixer.js +0 -150
  567. package/dist/tools/move-file.d.ts +0 -26
  568. package/dist/tools/move-file.d.ts.map +0 -1
  569. package/dist/tools/move-file.js +0 -58
  570. package/dist/tools/read-multiple-files.d.ts +0 -26
  571. package/dist/tools/read-multiple-files.d.ts.map +0 -1
  572. package/dist/tools/read-multiple-files.js +0 -139
  573. package/dist/tools/web-fetch.d.ts +0 -56
  574. package/dist/tools/web-fetch.d.ts.map +0 -1
  575. package/dist/tools/web-fetch.js +0 -247
  576. package/dist/tools/web-search.d.ts +0 -23
  577. package/dist/tools/web-search.d.ts.map +0 -1
  578. package/dist/tools/web-search.js +0 -133
  579. package/dist/tui/components/prompt-status.d.ts +0 -16
  580. package/dist/tui/components/prompt-status.d.ts.map +0 -1
  581. package/dist/tui/components/prompt-status.js +0 -21
  582. package/dist/utils/filesystem.d.ts.map +0 -1
  583. package/dist/utils/zod-utils.d.ts.map +0 -1
  584. package/dist/utils/zod-utils.js +0 -7
  585. /package/dist/{terminal → commands/add-directory}/types.js +0 -0
  586. /package/dist/utils/{filesystem.js → filesystem/operations.js} +0 -0
@@ -1,12 +1,158 @@
1
+ import { isCtrlC, isEnter, isEscape, isNavigationKey, isTab, } from "../../terminal/keys.js";
2
+ import { getSegmenter } from "../../terminal/segmenter.js";
1
3
  import style from "../../terminal/style.js";
4
+ import { visibleWidth } from "../utils.js";
2
5
  import { SelectList } from "./select-list.js";
6
+ // Cache for line metrics to avoid repeated segmentation (LRU-style)
7
+ const lineMetricsCache = {
8
+ maxSize: 1000,
9
+ cache: new Map(),
10
+ get(line) {
11
+ const cached = this.cache.get(line);
12
+ if (cached) {
13
+ // Move to end for LRU behavior (most recently used)
14
+ this.cache.delete(line);
15
+ this.cache.set(line, cached);
16
+ return cached;
17
+ }
18
+ // Compute metrics
19
+ let result;
20
+ // Fast path for ASCII-only lines (common case)
21
+ if (/^[\x20-\x7E\t]*$/.test(line)) {
22
+ const graphemes = line.split("");
23
+ const widths = graphemes.map((char) => (char === "\t" ? 3 : 1));
24
+ const totalWidth = widths.reduce((sum, w) => sum + w, 0);
25
+ result = { graphemes, widths, totalWidth };
26
+ }
27
+ else {
28
+ // Complex Unicode line, use shared segmenter
29
+ const graphemes = [...getSegmenter().segment(line)].map((seg) => seg.segment);
30
+ const widths = graphemes.map((g) => visibleWidth(g));
31
+ const totalWidth = widths.reduce((sum, w) => sum + w, 0);
32
+ result = { graphemes, widths, totalWidth };
33
+ }
34
+ // Enforce size limit before adding (evict oldest = first entry)
35
+ if (this.cache.size >= this.maxSize) {
36
+ const firstKey = this.cache.keys().next().value;
37
+ if (firstKey !== undefined) {
38
+ this.cache.delete(firstKey);
39
+ }
40
+ }
41
+ this.cache.set(line, result);
42
+ return result;
43
+ },
44
+ clear() {
45
+ this.cache.clear();
46
+ },
47
+ };
48
+ /**
49
+ * Compute word-wrapped chunks for a line that exceeds the given width.
50
+ * Shared between layoutText() and buildVisualLineMap() to avoid duplication.
51
+ */
52
+ function computeLineChunks(line, maxWidth) {
53
+ const chunks = [];
54
+ let currentChunk = "";
55
+ let currentWidth = 0;
56
+ let chunkStartIndex = 0;
57
+ let currentIndex = 0;
58
+ // Split into words (separated by whitespace) while preserving separators
59
+ const wordPattern = /(\s+)|(\S+\s*)|(\S)/g;
60
+ const parts = [];
61
+ let match = wordPattern.exec(line);
62
+ let pos = 0;
63
+ while (match !== null) {
64
+ const matchedText = match[0];
65
+ const isWhitespace = /^\s*$/.test(matchedText);
66
+ const width = matchedText
67
+ .split("")
68
+ .reduce((sum, c) => sum + (c === "\t" ? 3 : 1), 0);
69
+ parts.push({ text: matchedText, isWord: !isWhitespace, width });
70
+ pos = match.index + matchedText.length;
71
+ match = wordPattern.exec(line);
72
+ }
73
+ // Handle remaining text after last match
74
+ if (pos < line.length) {
75
+ const remaining = line.slice(pos);
76
+ const width = remaining
77
+ .split("")
78
+ .reduce((sum, c) => sum + (c === "\t" ? 3 : 1), 0);
79
+ parts.push({ text: remaining, isWord: true, width });
80
+ }
81
+ for (const part of parts) {
82
+ const wouldExceed = currentWidth + part.width > maxWidth;
83
+ const isWordPart = part.isWord && currentChunk.length > 0 && !currentChunk.endsWith(" ");
84
+ if (wouldExceed && isWordPart && currentChunk.trimEnd().length > 0) {
85
+ // Current word would overflow - break before this word
86
+ chunks.push({
87
+ text: currentChunk,
88
+ startIndex: chunkStartIndex,
89
+ endIndex: currentIndex,
90
+ width: currentWidth,
91
+ });
92
+ currentChunk = part.text;
93
+ currentWidth = part.width;
94
+ chunkStartIndex = currentIndex;
95
+ }
96
+ else if (wouldExceed && currentChunk !== "") {
97
+ // Non-word or start of line overflow - break here
98
+ chunks.push({
99
+ text: currentChunk,
100
+ startIndex: chunkStartIndex,
101
+ endIndex: currentIndex,
102
+ width: currentWidth,
103
+ });
104
+ currentChunk = part.text;
105
+ currentWidth = part.width;
106
+ chunkStartIndex = currentIndex;
107
+ }
108
+ else {
109
+ // Add to current chunk
110
+ currentChunk += part.text;
111
+ currentWidth += part.width;
112
+ }
113
+ currentIndex += part.text.length;
114
+ }
115
+ // Push the last chunk
116
+ if (currentChunk !== "") {
117
+ chunks.push({
118
+ text: currentChunk,
119
+ startIndex: chunkStartIndex,
120
+ endIndex: currentIndex,
121
+ width: currentWidth,
122
+ });
123
+ }
124
+ return chunks;
125
+ }
126
+ /**
127
+ * Text editor component with support for multi-line input and autocomplete.
128
+ *
129
+ * Key bindings:
130
+ * - Enter: Create new line
131
+ * - Shift+Enter / Ctrl+Enter / Option+Enter: Submit prompt
132
+ * - Tab: Trigger autocomplete
133
+ * - Escape: Cancel autocomplete or custom handler
134
+ * - Ctrl+C: Custom handler
135
+ * - Arrow keys: Navigate text
136
+ * - Backspace/Delete: Delete characters
137
+ * - Ctrl+A: Move to start of line
138
+ * - Ctrl+E: Move to end of line
139
+ * - Ctrl+K: Delete to end of line
140
+ * - Ctrl+U: Delete to start of line
141
+ * - Ctrl+W / Option+Backspace: Delete word backwards
142
+ * - Ctrl+Left/Right / Option+Left/Right: Word navigation
143
+ * - Up/Down: History navigation when editor is empty
144
+ */
3
145
  export class Editor {
4
146
  state = {
5
147
  lines: [""],
6
148
  cursorLine: 0,
7
149
  cursorCol: 0,
8
150
  };
9
- config = {};
151
+ theme;
152
+ // Store last render width for cursor navigation
153
+ lastWidth = 80;
154
+ // Border color (can be changed dynamically)
155
+ borderColor;
10
156
  // Autocomplete support
11
157
  autocompleteProvider;
12
158
  autocompleteList;
@@ -19,26 +165,87 @@ export class Editor {
19
165
  // Bracketed paste mode buffering
20
166
  pasteBuffer = "";
21
167
  isInPaste = false;
168
+ // Prompt history for up/down navigation
169
+ history = [];
170
+ historyIndex = -1; // -1 = not browsing, 0 = most recent, 1 = older, etc.
22
171
  onSubmit;
23
172
  onChange;
24
173
  disableSubmit = false;
25
174
  // Custom key handlers for coding-agent
26
175
  onEscape;
27
- onCtrlC;
28
176
  onRenderRequested;
29
- constructor(config) {
30
- if (config) {
31
- this.config = { ...this.config, ...config };
32
- }
177
+ constructor(theme) {
178
+ // Default theme if none provided (backward compatibility)
179
+ this.theme = theme || {
180
+ borderColor: style.gray,
181
+ };
182
+ this.borderColor = this.theme.borderColor;
33
183
  }
34
- configure(config) {
35
- this.config = { ...this.config, ...config };
184
+ /**
185
+ * Add a prompt to history for up/down arrow navigation.
186
+ * Called after successful submission.
187
+ */
188
+ addToHistory(text) {
189
+ const trimmed = text.trim();
190
+ if (!trimmed)
191
+ return;
192
+ // Don't add consecutive duplicates
193
+ if (this.history.length > 0 && this.history[0] === trimmed)
194
+ return;
195
+ this.history.unshift(trimmed);
196
+ // Limit history size
197
+ if (this.history.length > 100) {
198
+ this.history.pop();
199
+ }
36
200
  }
37
201
  setAutocompleteProvider(provider) {
38
202
  this.autocompleteProvider = provider;
39
203
  }
204
+ isEditorEmpty() {
205
+ return this.state.lines.length === 1 && this.state.lines[0] === "";
206
+ }
207
+ isOnFirstVisualLine() {
208
+ const visualLines = this.buildVisualLineMap(this.lastWidth);
209
+ const currentVisualLine = this.findCurrentVisualLine(visualLines);
210
+ return currentVisualLine === 0;
211
+ }
212
+ isOnLastVisualLine() {
213
+ const visualLines = this.buildVisualLineMap(this.lastWidth);
214
+ const currentVisualLine = this.findCurrentVisualLine(visualLines);
215
+ return currentVisualLine === visualLines.length - 1;
216
+ }
217
+ navigateHistory(direction) {
218
+ if (this.history.length === 0)
219
+ return;
220
+ const newIndex = this.historyIndex - direction; // Up(-1) increases index, Down(1) decreases
221
+ if (newIndex < -1 || newIndex >= this.history.length)
222
+ return;
223
+ this.historyIndex = newIndex;
224
+ if (this.historyIndex === -1) {
225
+ // Returned to "current" state - clear editor
226
+ this.setTextInternal("");
227
+ }
228
+ else {
229
+ this.setTextInternal(this.history[this.historyIndex] || "");
230
+ }
231
+ }
232
+ /** Internal setText that doesn't reset history state - used by navigateHistory */
233
+ setTextInternal(text) {
234
+ const lines = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
235
+ this.state.lines = lines.length === 0 ? [""] : lines;
236
+ this.state.cursorLine = this.state.lines.length - 1;
237
+ this.state.cursorCol = this.state.lines[this.state.cursorLine]?.length || 0;
238
+ if (this.onChange) {
239
+ this.onChange(this.getText());
240
+ }
241
+ }
242
+ invalidate() {
243
+ // No cached state to invalidate currently
244
+ }
40
245
  render(width) {
41
- const horizontal = style.gray("─");
246
+ // Store width for cursor navigation
247
+ this.lastWidth = width;
248
+ const horizontal = this.borderColor("─");
42
249
  // Layout the text - use full width
43
250
  const layoutLines = this.layoutText(width);
44
251
  const result = [];
@@ -47,41 +254,50 @@ export class Editor {
47
254
  // Render each layout line
48
255
  for (const layoutLine of layoutLines) {
49
256
  let displayText = layoutLine.text;
50
- let visibleLength = layoutLine.text.length;
257
+ let lineVisibleWidth = layoutLine.width;
51
258
  // Add cursor if this line has it
52
259
  if (layoutLine.hasCursor && layoutLine.cursorPos !== undefined) {
53
260
  const before = displayText.slice(0, layoutLine.cursorPos);
54
261
  const after = displayText.slice(layoutLine.cursorPos);
55
262
  if (after.length > 0) {
56
- // Cursor is on a character - replace it with highlighted version
57
- const cursor = `\x1b[7m${after[0]}\x1b[0m`;
58
- const restAfter = after.slice(1);
263
+ // Cursor is on a character (grapheme) - replace it with highlighted version
264
+ // Get the first grapheme from 'after'
265
+ const afterGraphemes = [...getSegmenter().segment(after)];
266
+ const firstGrapheme = afterGraphemes[0]?.segment || "";
267
+ const restAfter = after.slice(firstGrapheme.length);
268
+ const cursor = `\x1b[7m${firstGrapheme}\x1b[0m`;
59
269
  displayText = before + cursor + restAfter;
60
- // visibleLength stays the same - we're replacing, not adding
270
+ // lineVisibleWidth stays the same - we're replacing, not adding
61
271
  }
62
272
  else {
63
273
  // Cursor is at the end - check if we have room for the space
64
- if (layoutLine.text.length < width) {
274
+ if (lineVisibleWidth < width) {
65
275
  // We have room - add highlighted space
66
276
  const cursor = "\x1b[7m \x1b[0m";
67
277
  displayText = before + cursor;
68
- // visibleLength increases by 1 - we're adding a space
69
- visibleLength = layoutLine.text.length + 1;
278
+ // lineVisibleWidth increases by 1 - we're adding a space
279
+ lineVisibleWidth = lineVisibleWidth + 1;
70
280
  }
71
281
  else {
72
- // Line is at full width - use reverse video on last character if possible
282
+ // Line is at full width - use reverse video on last grapheme if possible
73
283
  // or just show cursor at the end without adding space
74
- if (before.length > 0) {
75
- const lastChar = before[before.length - 1];
76
- const cursor = `\x1b[7m${lastChar}\x1b[0m`;
77
- displayText = before.slice(0, -1) + cursor;
284
+ const beforeGraphemes = [...getSegmenter().segment(before)];
285
+ if (beforeGraphemes.length > 0) {
286
+ const lastGrapheme = beforeGraphemes[beforeGraphemes.length - 1]?.segment || "";
287
+ const cursor = `\x1b[7m${lastGrapheme}\x1b[0m`;
288
+ // Rebuild 'before' without the last grapheme
289
+ const beforeWithoutLast = beforeGraphemes
290
+ .slice(0, -1)
291
+ .map((g) => g.segment)
292
+ .join("");
293
+ displayText = beforeWithoutLast + cursor;
78
294
  }
79
- // visibleLength stays the same
295
+ // lineVisibleWidth stays the same
80
296
  }
81
297
  }
82
298
  }
83
- // Calculate padding based on actual visible length
84
- const padding = " ".repeat(Math.max(0, width - visibleLength));
299
+ // Calculate padding based on actual visible width
300
+ const padding = " ".repeat(Math.max(0, width - lineVisibleWidth));
85
301
  // Render the line (no side borders, just horizontal lines above and below)
86
302
  result.push(displayText + padding);
87
303
  }
@@ -134,13 +350,12 @@ export class Editor {
134
350
  }
135
351
  // Intercept Escape key - but only if autocomplete is NOT active
136
352
  // (let parent handle escape for autocomplete cancellation)
137
- if (data === "\x1b" && this.onEscape && !this.isShowingAutocomplete()) {
353
+ if (isEscape(data) && this.onEscape && !this.isShowingAutocomplete()) {
138
354
  this.onEscape();
139
355
  return;
140
356
  }
141
357
  // Intercept Ctrl+C
142
- if (data === "\x03" && this.onCtrlC) {
143
- this.onCtrlC();
358
+ if (isCtrlC(data)) {
144
359
  return;
145
360
  }
146
361
  // Process regular input data
@@ -155,49 +370,54 @@ export class Editor {
155
370
  // Handle autocomplete special keys first (but don't block other input)
156
371
  if (this.isAutocompleting && this.autocompleteList) {
157
372
  // Escape - cancel autocomplete
158
- if (data === "\x1b") {
373
+ if (isEscape(data)) {
159
374
  this.cancelAutocomplete();
160
375
  return;
161
376
  }
162
- // Let the autocomplete list handle navigation and selection
163
- if (data === "\x1b[A" ||
164
- data === "\x1b[B" ||
165
- data === "\r" ||
166
- data === "\t") {
167
- // Pass arrow keys and Tab to the list for navigation
168
- if (data === "\x1b[A" || data === "\x1b[B" || data === "\t") {
169
- this.autocompleteList.handleInput(data);
170
- }
171
- // Only Enter applies the selection
172
- if (data === "\r") {
173
- const selected = this.autocompleteList.getSelectedItem();
174
- if (selected && this.autocompleteProvider) {
175
- const result = this.autocompleteProvider.applyCompletion(this.state.lines, this.state.cursorLine, this.state.cursorCol, selected, this.autocompletePrefix);
176
- this.state.lines = result.lines;
177
- this.state.cursorLine = result.cursorLine;
178
- this.state.cursorCol = result.cursorCol;
179
- this.cancelAutocomplete();
180
- if (this.onChange) {
181
- this.onChange(this.getText());
182
- }
377
+ // Enter - apply selection
378
+ if (isEnter(data)) {
379
+ const selected = this.autocompleteList.getSelectedItem();
380
+ if (selected && this.autocompleteProvider) {
381
+ const result = this.autocompleteProvider.applyCompletion(this.state.lines, this.state.cursorLine, this.state.cursorCol, selected, this.autocompletePrefix);
382
+ this.state.lines = result.lines;
383
+ this.state.cursorLine = result.cursorLine;
384
+ this.state.cursorCol = result.cursorCol;
385
+ this.cancelAutocomplete();
386
+ if (this.onChange) {
387
+ this.onChange(this.getText());
183
388
  }
184
- return;
185
389
  }
186
- // For other keys, handle normally within autocomplete
390
+ return;
391
+ }
392
+ // Navigation keys (arrows, Tab, Shift+Tab) - pass to autocomplete list
393
+ if (isNavigationKey(data)) {
394
+ this.autocompleteList.handleInput(data);
187
395
  return;
188
396
  }
189
397
  // For other keys (like regular typing), DON'T return here
190
398
  // Let them fall through to normal character handling
191
399
  }
192
400
  // Tab key - context-aware completion (but not when already autocompleting)
193
- if (data === "\t" && !this.isAutocompleting) {
401
+ if (isTab(data) && !this.isAutocompleting) {
194
402
  void this.handleTabCompletion();
195
403
  return;
196
404
  }
197
405
  // Continue with rest of input handling
198
- // Ctrl+K - Delete current line
406
+ // Ctrl+K - Delete to end of line
199
407
  if (data.charCodeAt(0) === 11) {
200
- this.deleteCurrentLine();
408
+ this.deleteToEndOfLine();
409
+ }
410
+ // Ctrl+U - Delete to start of line
411
+ else if (data.charCodeAt(0) === 21) {
412
+ this.deleteToStartOfLine();
413
+ }
414
+ // Ctrl+W - Delete word backwards
415
+ else if (data.charCodeAt(0) === 23) {
416
+ this.deleteWordBackwards();
417
+ }
418
+ // Option/Alt+Backspace (e.g. Ghostty sends ESC + DEL)
419
+ else if (data === "\x1b\x7f") {
420
+ this.deleteWordBackwards();
201
421
  }
202
422
  // Ctrl+A - Move to start of line
203
423
  else if (data.charCodeAt(0) === 1) {
@@ -207,13 +427,12 @@ export class Editor {
207
427
  else if (data.charCodeAt(0) === 5) {
208
428
  this.moveToLineEnd();
209
429
  }
210
- // Modified Enter keys (Shift+Enter, Ctrl+Enter, etc.) - create new line
211
- else if (this.isModifiedEnter(data)) {
212
- // Modifier + Enter = new line
430
+ // Plain Enter (char code 13 for CR) - create new line
431
+ else if (data.charCodeAt(0) === 13 && data.length === 1) {
213
432
  this.addNewLine();
214
433
  }
215
- // Plain Enter (char code 13 for CR) - only CR submits, LF adds new line
216
- else if (data.charCodeAt(0) === 13 && data.length === 1) {
434
+ // Modified Enter keys (Shift+Enter, Ctrl+Enter, etc.) - submit
435
+ else if (this.isModifiedEnter(data)) {
217
436
  // If submit is disabled, do nothing
218
437
  if (this.disableSubmit) {
219
438
  return;
@@ -234,6 +453,7 @@ export class Editor {
234
453
  };
235
454
  this.pastes.clear();
236
455
  this.pasteCounter = 0;
456
+ this.historyIndex = -1; // Exit history browsing mode
237
457
  // Notify that editor is now empty
238
458
  if (this.onChange) {
239
459
  this.onChange("");
@@ -260,14 +480,42 @@ export class Editor {
260
480
  // Delete key
261
481
  this.handleForwardDelete();
262
482
  }
483
+ // Word navigation (Option/Alt + Arrow or Ctrl + Arrow)
484
+ // Option+Left: \x1b[1;3D or \x1bb
485
+ // Option+Right: \x1b[1;3C or \x1bf
486
+ // Ctrl+Left: \x1b[1;5D
487
+ // Ctrl+Right: \x1b[1;5C
488
+ else if (data === "\x1b[1;3D" || data === "\x1bb" || data === "\x1b[1;5D") {
489
+ // Word left
490
+ this.moveWordBackwards();
491
+ }
492
+ else if (data === "\x1b[1;3C" ||
493
+ data === "\x1bf" ||
494
+ data === "\x1b[1;5C") {
495
+ // Word right
496
+ this.moveWordForwards();
497
+ }
263
498
  // Arrow keys
264
499
  else if (data === "\x1b[A") {
265
- // Up
266
- this.moveCursor(-1, 0);
500
+ // Up - history navigation or cursor movement
501
+ if (this.isEditorEmpty()) {
502
+ this.navigateHistory(-1); // Start browsing history
503
+ }
504
+ else if (this.historyIndex > -1 && this.isOnFirstVisualLine()) {
505
+ this.navigateHistory(-1); // Navigate to older history entry
506
+ }
507
+ else {
508
+ this.moveCursor(-1, 0); // Cursor movement (within text or history entry)
509
+ }
267
510
  }
268
511
  else if (data === "\x1b[B") {
269
- // Down
270
- this.moveCursor(1, 0);
512
+ // Down - history navigation or cursor movement
513
+ if (this.historyIndex > -1 && this.isOnLastVisualLine()) {
514
+ this.navigateHistory(1); // Navigate to newer history entry or clear
515
+ }
516
+ else {
517
+ this.moveCursor(1, 0); // Cursor movement (within text or history entry)
518
+ }
271
519
  }
272
520
  else if (data === "\x1b[C") {
273
521
  // Right
@@ -277,8 +525,8 @@ export class Editor {
277
525
  // Left
278
526
  this.moveCursor(0, -1);
279
527
  }
280
- // Regular characters (printable ASCII)
281
- else if (data.charCodeAt(0) >= 32 && data.charCodeAt(0) <= 126) {
528
+ // Regular characters (printable characters and unicode, but not control characters)
529
+ else if (data.charCodeAt(0) >= 32) {
282
530
  this.insertCharacter(data);
283
531
  }
284
532
  }
@@ -291,6 +539,7 @@ export class Editor {
291
539
  text: "",
292
540
  hasCursor: true,
293
541
  cursorPos: 0,
542
+ width: 0,
294
543
  });
295
544
  return layoutLines;
296
545
  }
@@ -298,48 +547,54 @@ export class Editor {
298
547
  for (let i = 0; i < this.state.lines.length; i++) {
299
548
  const line = this.state.lines[i] || "";
300
549
  const isCurrentLine = i === this.state.cursorLine;
301
- const maxLineLength = contentWidth;
302
- if (line.length <= maxLineLength) {
550
+ const metrics = lineMetricsCache.get(line);
551
+ const lineVisibleWidth = metrics.totalWidth;
552
+ if (lineVisibleWidth <= contentWidth) {
303
553
  // Line fits in one layout line
304
554
  if (isCurrentLine) {
305
555
  layoutLines.push({
306
556
  text: line,
307
557
  hasCursor: true,
308
558
  cursorPos: this.state.cursorCol,
559
+ width: lineVisibleWidth,
309
560
  });
310
561
  }
311
562
  else {
312
563
  layoutLines.push({
313
564
  text: line,
314
565
  hasCursor: false,
566
+ width: lineVisibleWidth,
315
567
  });
316
568
  }
317
569
  }
318
570
  else {
319
- // Line needs wrapping
320
- const chunks = [];
321
- for (let pos = 0; pos < line.length; pos += maxLineLength) {
322
- chunks.push(line.slice(pos, pos + maxLineLength));
323
- }
571
+ // Line needs wrapping - use shared helper
572
+ const chunks = computeLineChunks(line, contentWidth);
324
573
  for (let chunkIndex = 0; chunkIndex < chunks.length; chunkIndex++) {
325
574
  const chunk = chunks[chunkIndex];
326
575
  if (!chunk)
327
576
  continue;
328
- const chunkStart = chunkIndex * maxLineLength;
329
- const chunkEnd = chunkStart + chunk.length;
330
577
  const cursorPos = this.state.cursorCol;
331
- const hasCursorInChunk = isCurrentLine && cursorPos >= chunkStart && cursorPos <= chunkEnd;
578
+ const isLastChunk = chunkIndex === chunks.length - 1;
579
+ // For non-last chunks, cursor at endIndex belongs to the next chunk
580
+ const hasCursorInChunk = isCurrentLine &&
581
+ cursorPos >= chunk.startIndex &&
582
+ (isLastChunk
583
+ ? cursorPos <= chunk.endIndex
584
+ : cursorPos < chunk.endIndex);
332
585
  if (hasCursorInChunk) {
333
586
  layoutLines.push({
334
- text: chunk,
587
+ text: chunk.text,
335
588
  hasCursor: true,
336
- cursorPos: cursorPos - chunkStart,
589
+ cursorPos: cursorPos - chunk.startIndex,
590
+ width: chunk.width,
337
591
  });
338
592
  }
339
593
  else {
340
594
  layoutLines.push({
341
- text: chunk,
595
+ text: chunk.text,
342
596
  hasCursor: false,
597
+ width: chunk.width,
343
598
  });
344
599
  }
345
600
  }
@@ -351,20 +606,12 @@ export class Editor {
351
606
  return this.state.lines.join("\n");
352
607
  }
353
608
  setText(text) {
354
- // Split text into lines, handling different line endings
355
- const lines = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
356
- // Ensure at least one empty line
357
- this.state.lines = lines.length === 0 ? [""] : lines;
358
- // Reset cursor to end of text
359
- this.state.cursorLine = this.state.lines.length - 1;
360
- this.state.cursorCol = this.state.lines[this.state.cursorLine]?.length || 0;
361
- // Notify of change
362
- if (this.onChange) {
363
- this.onChange(this.getText());
364
- }
609
+ this.historyIndex = -1; // Exit history browsing mode
610
+ this.setTextInternal(text);
365
611
  }
366
612
  // All the editor methods from before...
367
613
  insertCharacter(char) {
614
+ this.historyIndex = -1; // Exit history browsing mode
368
615
  const line = this.state.lines[this.state.cursorLine] || "";
369
616
  const before = line.slice(0, this.state.cursorCol);
370
617
  const after = line.slice(this.state.cursorCol);
@@ -379,13 +626,44 @@ export class Editor {
379
626
  if (char === "/" && this.isAtStartOfMessage()) {
380
627
  void this.tryTriggerAutocomplete();
381
628
  }
629
+ // Auto-trigger for "@" file reference (fuzzy search)
630
+ else if (char === "@") {
631
+ const currentLine = this.state.lines[this.state.cursorLine] || "";
632
+ const textBeforeCursor = currentLine.slice(0, this.state.cursorCol);
633
+ // Only trigger if @ is after whitespace or at start of line
634
+ const charBeforeAt = textBeforeCursor[textBeforeCursor.length - 2];
635
+ if (textBeforeCursor.length === 1 ||
636
+ charBeforeAt === " " ||
637
+ charBeforeAt === "\t") {
638
+ void this.tryTriggerAutocomplete();
639
+ }
640
+ }
641
+ // Auto-trigger for "#" file search
642
+ else if (char === "#") {
643
+ const currentLine = this.state.lines[this.state.cursorLine] || "";
644
+ const textBeforeCursor = currentLine.slice(0, this.state.cursorCol);
645
+ // Only trigger if # is after whitespace or at start of line
646
+ const charBeforeHash = textBeforeCursor[textBeforeCursor.length - 2];
647
+ if (textBeforeCursor.length === 1 ||
648
+ charBeforeHash === " " ||
649
+ charBeforeHash === "\t") {
650
+ void this.tryTriggerAutocomplete();
651
+ }
652
+ }
382
653
  // Also auto-trigger when typing letters in a slash command context
383
654
  else if (/[a-zA-Z0-9]/.test(char)) {
384
655
  const currentLine = this.state.lines[this.state.cursorLine] || "";
385
656
  const textBeforeCursor = currentLine.slice(0, this.state.cursorCol);
386
- // Check if we're in a slash command with a space (i.e., typing arguments)
387
- if (textBeforeCursor.startsWith("/") &&
388
- textBeforeCursor.includes(" ")) {
657
+ // Check if we're in a slash command (with or without space for arguments)
658
+ if (textBeforeCursor.trimStart().startsWith("/")) {
659
+ void this.tryTriggerAutocomplete();
660
+ }
661
+ // Check if we're in an @ file reference context
662
+ else if (textBeforeCursor.match(/(?:^|[\s])@[^\s]*$/)) {
663
+ void this.tryTriggerAutocomplete();
664
+ }
665
+ // Check if we're in a # file search context
666
+ else if (textBeforeCursor.match(/(?:^|[\s])#[^\s]*$/)) {
389
667
  void this.tryTriggerAutocomplete();
390
668
  }
391
669
  }
@@ -501,6 +779,15 @@ export class Editor {
501
779
  if (this.isAutocompleting) {
502
780
  void this.updateAutocomplete();
503
781
  }
782
+ else {
783
+ // Check if we should trigger autocomplete after backspace in slash command context
784
+ const currentLine = this.state.lines[this.state.cursorLine] || "";
785
+ const textBeforeCursor = currentLine.slice(0, this.state.cursorCol);
786
+ // Trigger autocomplete if we're in a slash command context (typing command name)
787
+ if (textBeforeCursor.startsWith("/") && !textBeforeCursor.includes(" ")) {
788
+ void this.tryTriggerAutocomplete();
789
+ }
790
+ }
504
791
  }
505
792
  moveToLineStart() {
506
793
  this.state.cursorCol = 0;
@@ -527,45 +814,254 @@ export class Editor {
527
814
  this.onChange(this.getText());
528
815
  }
529
816
  }
530
- deleteCurrentLine() {
531
- if (this.state.lines.length === 1) {
532
- // Only one line - just clear it
533
- this.state.lines[0] = "";
817
+ deleteToStartOfLine() {
818
+ this.historyIndex = -1; // Exit history browsing mode
819
+ const currentLine = this.state.lines[this.state.cursorLine] || "";
820
+ if (this.state.cursorCol > 0) {
821
+ // Delete from start of line up to cursor
822
+ this.state.lines[this.state.cursorLine] = currentLine.slice(this.state.cursorCol);
534
823
  this.state.cursorCol = 0;
535
824
  }
536
- else {
537
- // Multiple lines - remove current line
825
+ else if (this.state.cursorLine > 0) {
826
+ // At start of line - merge with previous line
827
+ const previousLine = this.state.lines[this.state.cursorLine - 1] || "";
828
+ this.state.lines[this.state.cursorLine - 1] = previousLine + currentLine;
538
829
  this.state.lines.splice(this.state.cursorLine, 1);
539
- // Adjust cursor position
540
- if (this.state.cursorLine >= this.state.lines.length) {
541
- // Was on last line, move to new last line
542
- this.state.cursorLine = this.state.lines.length - 1;
830
+ this.state.cursorLine--;
831
+ this.state.cursorCol = previousLine.length;
832
+ }
833
+ if (this.onChange) {
834
+ this.onChange(this.getText());
835
+ }
836
+ }
837
+ deleteToEndOfLine() {
838
+ this.historyIndex = -1; // Exit history browsing mode
839
+ const currentLine = this.state.lines[this.state.cursorLine] || "";
840
+ if (this.state.cursorCol < currentLine.length) {
841
+ // Delete from cursor to end of line
842
+ this.state.lines[this.state.cursorLine] = currentLine.slice(0, this.state.cursorCol);
843
+ }
844
+ else if (this.state.cursorLine < this.state.lines.length - 1) {
845
+ // At end of line - merge with next line
846
+ const nextLine = this.state.lines[this.state.cursorLine + 1] || "";
847
+ this.state.lines[this.state.cursorLine] = currentLine + nextLine;
848
+ this.state.lines.splice(this.state.cursorLine + 1, 1);
849
+ }
850
+ if (this.onChange) {
851
+ this.onChange(this.getText());
852
+ }
853
+ }
854
+ deleteWordBackwards() {
855
+ this.historyIndex = -1; // Exit history browsing mode
856
+ const currentLine = this.state.lines[this.state.cursorLine] || "";
857
+ // If at start of line, behave like backspace at column 0 (merge with previous line)
858
+ if (this.state.cursorCol === 0) {
859
+ if (this.state.cursorLine > 0) {
860
+ const previousLine = this.state.lines[this.state.cursorLine - 1] || "";
861
+ this.state.lines[this.state.cursorLine - 1] =
862
+ previousLine + currentLine;
863
+ this.state.lines.splice(this.state.cursorLine, 1);
864
+ this.state.cursorLine--;
865
+ this.state.cursorCol = previousLine.length;
543
866
  }
544
- // Clamp cursor column to new line length
545
- const newLine = this.state.lines[this.state.cursorLine] || "";
546
- this.state.cursorCol = Math.min(this.state.cursorCol, newLine.length);
867
+ }
868
+ else {
869
+ const textBeforeCursor = currentLine.slice(0, this.state.cursorCol);
870
+ const isWhitespace = (char) => /\s/.test(char);
871
+ const isPunctuation = (char) => {
872
+ // Treat obvious code punctuation as boundaries
873
+ return /[(){}[\]<>.,;:'"!?+\-=*/\\|&%^$#@~`]/.test(char);
874
+ };
875
+ let deleteFrom = this.state.cursorCol;
876
+ const lastChar = textBeforeCursor[deleteFrom - 1] ?? "";
877
+ // If immediately on whitespace or punctuation, delete that single boundary char
878
+ if (isWhitespace(lastChar) || isPunctuation(lastChar)) {
879
+ deleteFrom -= 1;
880
+ }
881
+ else {
882
+ // Otherwise, delete a run of non-boundary characters (the "word")
883
+ while (deleteFrom > 0) {
884
+ const ch = textBeforeCursor[deleteFrom - 1] ?? "";
885
+ if (isWhitespace(ch) || isPunctuation(ch)) {
886
+ break;
887
+ }
888
+ deleteFrom -= 1;
889
+ }
890
+ }
891
+ this.state.lines[this.state.cursorLine] =
892
+ currentLine.slice(0, deleteFrom) +
893
+ currentLine.slice(this.state.cursorCol);
894
+ this.state.cursorCol = deleteFrom;
547
895
  }
548
896
  if (this.onChange) {
549
897
  this.onChange(this.getText());
550
898
  }
551
899
  }
900
+ /**
901
+ * Build a mapping from visual lines to logical positions.
902
+ * Returns an array where each element represents a visual line with:
903
+ * - logicalLine: index into this.state.lines
904
+ * - startCol: starting column in the logical line
905
+ * - length: length of this visual line segment
906
+ */
907
+ buildVisualLineMap(width) {
908
+ const visualLines = [];
909
+ for (let i = 0; i < this.state.lines.length; i++) {
910
+ const line = this.state.lines[i] || "";
911
+ const metrics = lineMetricsCache.get(line);
912
+ const lineVisWidth = metrics.totalWidth;
913
+ if (line.length === 0) {
914
+ // Empty line still takes one visual line
915
+ visualLines.push({ logicalLine: i, startCol: 0, length: 0 });
916
+ }
917
+ else if (lineVisWidth <= width) {
918
+ visualLines.push({ logicalLine: i, startCol: 0, length: line.length });
919
+ }
920
+ else {
921
+ // Line needs wrapping - use shared helper
922
+ const chunks = computeLineChunks(line, width);
923
+ for (const chunk of chunks) {
924
+ visualLines.push({
925
+ logicalLine: i,
926
+ startCol: chunk.startIndex,
927
+ length: chunk.endIndex - chunk.startIndex,
928
+ });
929
+ }
930
+ }
931
+ }
932
+ return visualLines;
933
+ }
934
+ /**
935
+ * Find the visual line index for the current cursor position.
936
+ */
937
+ findCurrentVisualLine(visualLines) {
938
+ for (let i = 0; i < visualLines.length; i++) {
939
+ const vl = visualLines[i];
940
+ if (!vl)
941
+ continue;
942
+ if (vl.logicalLine === this.state.cursorLine) {
943
+ const colInSegment = this.state.cursorCol - vl.startCol;
944
+ // Cursor is in this segment if it's within range
945
+ // For the last segment of a logical line, cursor can be at length (end position)
946
+ const isLastSegmentOfLine = i === visualLines.length - 1 ||
947
+ visualLines[i + 1]?.logicalLine !== vl.logicalLine;
948
+ if (colInSegment >= 0 &&
949
+ (colInSegment < vl.length ||
950
+ (isLastSegmentOfLine && colInSegment <= vl.length))) {
951
+ return i;
952
+ }
953
+ }
954
+ }
955
+ // Fallback: return last visual line
956
+ return visualLines.length - 1;
957
+ }
552
958
  moveCursor(deltaLine, deltaCol) {
959
+ const width = this.lastWidth;
553
960
  if (deltaLine !== 0) {
554
- const newLine = this.state.cursorLine + deltaLine;
555
- if (newLine >= 0 && newLine < this.state.lines.length) {
556
- this.state.cursorLine = newLine;
557
- // Clamp cursor column to new line length
558
- const line = this.state.lines[this.state.cursorLine] || "";
559
- this.state.cursorCol = Math.min(this.state.cursorCol, line.length);
961
+ // Build visual line map for navigation
962
+ const visualLines = this.buildVisualLineMap(width);
963
+ const currentVisualLine = this.findCurrentVisualLine(visualLines);
964
+ // Calculate column position within current visual line
965
+ const currentVl = visualLines[currentVisualLine];
966
+ const visualCol = currentVl
967
+ ? this.state.cursorCol - currentVl.startCol
968
+ : 0;
969
+ // Move to target visual line
970
+ const targetVisualLine = currentVisualLine + deltaLine;
971
+ if (targetVisualLine >= 0 && targetVisualLine < visualLines.length) {
972
+ const targetVl = visualLines[targetVisualLine];
973
+ if (targetVl) {
974
+ this.state.cursorLine = targetVl.logicalLine;
975
+ // Try to maintain visual column position, clamped to line length
976
+ const targetCol = targetVl.startCol + Math.min(visualCol, targetVl.length);
977
+ const logicalLine = this.state.lines[targetVl.logicalLine] || "";
978
+ this.state.cursorCol = Math.min(targetCol, logicalLine.length);
979
+ }
560
980
  }
561
981
  }
562
982
  if (deltaCol !== 0) {
563
- // Move column
564
- const newCol = this.state.cursorCol + deltaCol;
565
983
  const currentLine = this.state.lines[this.state.cursorLine] || "";
566
- const maxCol = currentLine.length;
567
- this.state.cursorCol = Math.max(0, Math.min(maxCol, newCol));
984
+ if (deltaCol > 0) {
985
+ // Moving right
986
+ if (this.state.cursorCol < currentLine.length) {
987
+ this.state.cursorCol++;
988
+ }
989
+ else if (this.state.cursorLine < this.state.lines.length - 1) {
990
+ // Wrap to start of next logical line
991
+ this.state.cursorLine++;
992
+ this.state.cursorCol = 0;
993
+ }
994
+ }
995
+ else {
996
+ // Moving left
997
+ if (this.state.cursorCol > 0) {
998
+ this.state.cursorCol--;
999
+ }
1000
+ else if (this.state.cursorLine > 0) {
1001
+ // Wrap to end of previous logical line
1002
+ this.state.cursorLine--;
1003
+ const prevLine = this.state.lines[this.state.cursorLine] || "";
1004
+ this.state.cursorCol = prevLine.length;
1005
+ }
1006
+ }
1007
+ }
1008
+ }
1009
+ isWordBoundary(char) {
1010
+ return /\s/.test(char) || /[(){}[\]<>.,;:'"!?+\-=*/\\|&%^$#@~`]/.test(char);
1011
+ }
1012
+ moveWordBackwards() {
1013
+ const currentLine = this.state.lines[this.state.cursorLine] || "";
1014
+ // If at start of line, move to end of previous line
1015
+ if (this.state.cursorCol === 0) {
1016
+ if (this.state.cursorLine > 0) {
1017
+ this.state.cursorLine--;
1018
+ const prevLine = this.state.lines[this.state.cursorLine] || "";
1019
+ this.state.cursorCol = prevLine.length;
1020
+ }
1021
+ return;
1022
+ }
1023
+ const textBeforeCursor = currentLine.slice(0, this.state.cursorCol);
1024
+ let newCol = this.state.cursorCol;
1025
+ const lastChar = textBeforeCursor[newCol - 1] ?? "";
1026
+ // If immediately on whitespace or punctuation, skip that single boundary char
1027
+ if (this.isWordBoundary(lastChar)) {
1028
+ newCol -= 1;
1029
+ }
1030
+ // Now skip the "word" (non-boundary characters)
1031
+ while (newCol > 0) {
1032
+ const ch = textBeforeCursor[newCol - 1] ?? "";
1033
+ if (this.isWordBoundary(ch)) {
1034
+ break;
1035
+ }
1036
+ newCol -= 1;
1037
+ }
1038
+ this.state.cursorCol = newCol;
1039
+ }
1040
+ moveWordForwards() {
1041
+ const currentLine = this.state.lines[this.state.cursorLine] || "";
1042
+ // If at end of line, move to start of next line
1043
+ if (this.state.cursorCol >= currentLine.length) {
1044
+ if (this.state.cursorLine < this.state.lines.length - 1) {
1045
+ this.state.cursorLine++;
1046
+ this.state.cursorCol = 0;
1047
+ }
1048
+ return;
1049
+ }
1050
+ let newCol = this.state.cursorCol;
1051
+ const charAtCursor = currentLine[newCol] ?? "";
1052
+ // If on whitespace or punctuation, skip it
1053
+ if (this.isWordBoundary(charAtCursor)) {
1054
+ newCol += 1;
1055
+ }
1056
+ // Skip the "word" (non-boundary characters)
1057
+ while (newCol < currentLine.length) {
1058
+ const ch = currentLine[newCol] ?? "";
1059
+ if (this.isWordBoundary(ch)) {
1060
+ break;
1061
+ }
1062
+ newCol += 1;
568
1063
  }
1064
+ this.state.cursorCol = newCol;
569
1065
  }
570
1066
  // Helper method to check if cursor is at start of message (for slash command detection)
571
1067
  isAtStartOfMessage() {
@@ -597,7 +1093,7 @@ export class Editor {
597
1093
  this.autocompleteList.updateItems(suggestions.items);
598
1094
  }
599
1095
  else {
600
- this.autocompleteList = new SelectList(suggestions.items, 5);
1096
+ this.autocompleteList = new SelectList(suggestions.items, 5, this.theme.selectList);
601
1097
  }
602
1098
  // Request re-render to show autocomplete list
603
1099
  this.onRenderRequested?.();
@@ -640,7 +1136,7 @@ export class Editor {
640
1136
  this.autocompleteList.updateItems(suggestions.items);
641
1137
  }
642
1138
  else {
643
- this.autocompleteList = new SelectList(suggestions.items, 5);
1139
+ this.autocompleteList = new SelectList(suggestions.items, 5, this.theme.selectList);
644
1140
  }
645
1141
  this.isAutocompleting = true;
646
1142
  // Request re-render to show autocomplete list
@@ -670,14 +1166,16 @@ export class Editor {
670
1166
  const currentLine = this.state.lines[this.state.cursorLine] || "";
671
1167
  const textBeforeCursor = currentLine.slice(0, this.state.cursorCol);
672
1168
  // If we're no longer in the context that triggered autocomplete, cancel it
673
- // For slash commands, check if we're still in slash command context
674
- // For file paths, check if we're still in the same path context
675
- if (textBeforeCursor.startsWith("/")) {
676
- // For slash commands, we should continue autocomplete as long as we're in slash command context
677
- // Don't cancel based on prefix matching for progressive typing
1169
+ // For slash commands, @ file attachments, and # file search, allow progressive typing
1170
+ // For other file paths, check if we're still in the same path context
1171
+ if (textBeforeCursor.startsWith("/") ||
1172
+ textBeforeCursor.match(/(?:^|[\s])@[^\s]*$/) ||
1173
+ textBeforeCursor.match(/(?:^|[\s])#[^\s]*$/)) {
1174
+ // For slash commands, @ file attachments, and # file search,
1175
+ // continue autocomplete as long as we're in the right context
678
1176
  }
679
1177
  else {
680
- // For file paths, check if we're still in the same path context
1178
+ // For other file paths, check if we're still in the same path context
681
1179
  if (!textBeforeCursor.endsWith(this.autocompletePrefix)) {
682
1180
  this.cancelAutocomplete();
683
1181
  return;
@@ -697,7 +1195,7 @@ export class Editor {
697
1195
  this.autocompleteList.updateItems(suggestions.items);
698
1196
  }
699
1197
  else {
700
- this.autocompleteList = new SelectList(suggestions.items, 5);
1198
+ this.autocompleteList = new SelectList(suggestions.items, 5, this.theme.selectList);
701
1199
  }
702
1200
  this.isAutocompleting = true;
703
1201
  // Request re-render to show updated autocomplete list
@@ -719,8 +1217,12 @@ export class Editor {
719
1217
  "\x1bOM", // Some terminals
720
1218
  "\\\r", // VS Code terminal
721
1219
  "\x1b\r", // Option+Enter (macOS)
1220
+ "\x1b[27;2;13~", // xterm shift+enter
1221
+ "\x1b[13;2u", // libtermkey shift+enter
722
1222
  // Ctrl+Enter sequences
723
1223
  "\x1b[13;5~", // Some terminals
1224
+ "\x1b[27;5;13~", // xterm ctrl+enter
1225
+ "\x1b[13;5u", // libtermkey ctrl+enter
724
1226
  ];
725
1227
  // Check for known sequences
726
1228
  if (sequences.includes(data)) {
@@ -732,8 +1234,9 @@ export class Editor {
732
1234
  (data.includes("\r") || data.includes("\n"))) {
733
1235
  return true;
734
1236
  }
735
- // Check for Ctrl+Enter (Ctrl + CR)
736
- if (data.charCodeAt(0) === 13 && data.length > 1) {
1237
+ // Check for Ctrl+Enter (Ctrl + CR) or Ctrl+Enter with LF
1238
+ if ((data.charCodeAt(0) === 13 || data.charCodeAt(0) === 10) &&
1239
+ data.length > 1) {
737
1240
  return true;
738
1241
  }
739
1242
  return false;