@aigne/cli 1.60.0-beta → 1.74.0-beta

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 (360) hide show
  1. package/README.md +12 -12
  2. package/dist/_virtual/rolldown_runtime.cjs +29 -0
  3. package/dist/bunwrapper.cjs +22 -0
  4. package/dist/bunwrapper.d.cts +1 -0
  5. package/dist/bunwrapper.d.mts +1 -0
  6. package/dist/bunwrapper.mjs +23 -0
  7. package/dist/bunwrapper.mjs.map +1 -0
  8. package/dist/cli.cjs +42 -0
  9. package/dist/cli.d.cts +9 -0
  10. package/dist/cli.d.cts.map +1 -0
  11. package/dist/cli.d.mts +10 -0
  12. package/dist/cli.d.mts.map +1 -0
  13. package/dist/cli.mjs +41 -0
  14. package/dist/cli.mjs.map +1 -0
  15. package/dist/commands/aigne.cjs +23 -0
  16. package/dist/commands/aigne.mjs +22 -0
  17. package/dist/commands/aigne.mjs.map +1 -0
  18. package/dist/commands/app/agent.cjs +117 -0
  19. package/dist/commands/app/agent.mjs +113 -0
  20. package/dist/commands/app/agent.mjs.map +1 -0
  21. package/dist/commands/app/app.cjs +92 -0
  22. package/dist/commands/app/app.mjs +90 -0
  23. package/dist/commands/app/app.mjs.map +1 -0
  24. package/dist/commands/app/cli.cjs +6 -0
  25. package/dist/commands/app/cli.d.cts +1 -0
  26. package/dist/commands/app/cli.d.mts +1 -0
  27. package/dist/commands/app/cli.mjs +8 -0
  28. package/dist/commands/app/cli.mjs.map +1 -0
  29. package/dist/commands/app/upgrade.cjs +243 -0
  30. package/dist/commands/app/upgrade.mjs +240 -0
  31. package/dist/commands/app/upgrade.mjs.map +1 -0
  32. package/dist/commands/app.cjs +53 -0
  33. package/dist/commands/app.mjs +53 -0
  34. package/dist/commands/app.mjs.map +1 -0
  35. package/dist/commands/create.cjs +66 -0
  36. package/dist/commands/create.mjs +65 -0
  37. package/dist/commands/create.mjs.map +1 -0
  38. package/dist/commands/deploy.cjs +237 -0
  39. package/dist/commands/deploy.mjs +237 -0
  40. package/dist/commands/deploy.mjs.map +1 -0
  41. package/dist/commands/eval.cjs +88 -0
  42. package/dist/commands/eval.mjs +88 -0
  43. package/dist/commands/eval.mjs.map +1 -0
  44. package/dist/commands/hub.cjs +297 -0
  45. package/dist/commands/hub.mjs +294 -0
  46. package/dist/commands/hub.mjs.map +1 -0
  47. package/dist/commands/observe.cjs +49 -0
  48. package/dist/commands/observe.mjs +46 -0
  49. package/dist/commands/observe.mjs.map +1 -0
  50. package/dist/commands/run-skill.cjs +84 -0
  51. package/dist/commands/run-skill.mjs +81 -0
  52. package/dist/commands/run-skill.mjs.map +1 -0
  53. package/dist/commands/run.cjs +172 -0
  54. package/dist/commands/run.mjs +171 -0
  55. package/dist/commands/run.mjs.map +1 -0
  56. package/dist/commands/serve-mcp.cjs +68 -0
  57. package/dist/commands/serve-mcp.mjs +67 -0
  58. package/dist/commands/serve-mcp.mjs.map +1 -0
  59. package/dist/commands/test.cjs +40 -0
  60. package/dist/commands/test.mjs +39 -0
  61. package/dist/commands/test.mjs.map +1 -0
  62. package/dist/constants.cjs +28 -0
  63. package/dist/constants.d.cts +9 -0
  64. package/dist/constants.d.cts.map +1 -0
  65. package/dist/constants.d.mts +9 -0
  66. package/dist/constants.d.mts.map +1 -0
  67. package/dist/constants.mjs +24 -0
  68. package/dist/constants.mjs.map +1 -0
  69. package/dist/global.d.cjs +0 -0
  70. package/dist/global.d.cts +6 -0
  71. package/dist/global.d.cts.map +1 -0
  72. package/dist/global.d.mts +6 -0
  73. package/dist/global.d.mts.map +1 -0
  74. package/dist/index.cjs +0 -0
  75. package/dist/index.d.cts +2 -0
  76. package/dist/index.d.mts +2 -0
  77. package/dist/index.mjs +1 -0
  78. package/dist/tracer/terminal.cjs +336 -0
  79. package/dist/tracer/terminal.mjs +332 -0
  80. package/dist/tracer/terminal.mjs.map +1 -0
  81. package/dist/type.cjs +0 -0
  82. package/dist/type.d.cts +10 -0
  83. package/dist/type.d.cts.map +1 -0
  84. package/dist/type.d.mts +10 -0
  85. package/dist/type.d.mts.map +1 -0
  86. package/dist/type.mjs +1 -0
  87. package/dist/ui/utils/terminal-input.cjs +145 -0
  88. package/dist/ui/utils/terminal-input.mjs +144 -0
  89. package/dist/ui/utils/terminal-input.mjs.map +1 -0
  90. package/dist/ui/utils/text-buffer.cjs +865 -0
  91. package/dist/ui/utils/text-buffer.mjs +865 -0
  92. package/dist/ui/utils/text-buffer.mjs.map +1 -0
  93. package/dist/ui/utils/text-utils.cjs +85 -0
  94. package/dist/ui/utils/text-utils.mjs +78 -0
  95. package/dist/ui/utils/text-utils.mjs.map +1 -0
  96. package/dist/utils/agent-v1.cjs +180 -0
  97. package/dist/utils/agent-v1.d.cts +138 -0
  98. package/dist/utils/agent-v1.d.cts.map +1 -0
  99. package/dist/utils/agent-v1.d.mts +138 -0
  100. package/dist/utils/agent-v1.d.mts.map +1 -0
  101. package/dist/utils/agent-v1.mjs +179 -0
  102. package/dist/utils/agent-v1.mjs.map +1 -0
  103. package/dist/utils/aigne-hub/constants.cjs +22 -0
  104. package/dist/utils/aigne-hub/constants.mjs +18 -0
  105. package/dist/utils/aigne-hub/constants.mjs.map +1 -0
  106. package/dist/utils/aigne-hub/credential.cjs +179 -0
  107. package/dist/utils/aigne-hub/credential.mjs +175 -0
  108. package/dist/utils/aigne-hub/credential.mjs.map +1 -0
  109. package/dist/utils/aigne-hub/crypto.cjs +41 -0
  110. package/dist/utils/aigne-hub/crypto.mjs +33 -0
  111. package/dist/utils/aigne-hub/crypto.mjs.map +1 -0
  112. package/dist/utils/aigne-hub/model.cjs +112 -0
  113. package/dist/utils/aigne-hub/model.d.cts +19 -0
  114. package/dist/utils/aigne-hub/model.d.cts.map +1 -0
  115. package/dist/utils/aigne-hub/model.d.mts +19 -0
  116. package/dist/utils/aigne-hub/model.d.mts.map +1 -0
  117. package/dist/utils/aigne-hub/model.mjs +106 -0
  118. package/dist/utils/aigne-hub/model.mjs.map +1 -0
  119. package/dist/utils/aigne-hub/store/file.cjs +64 -0
  120. package/dist/utils/aigne-hub/store/file.mjs +64 -0
  121. package/dist/utils/aigne-hub/store/file.mjs.map +1 -0
  122. package/dist/utils/aigne-hub/store/index.cjs +37 -0
  123. package/dist/utils/aigne-hub/store/index.mjs +37 -0
  124. package/dist/utils/aigne-hub/store/index.mjs.map +1 -0
  125. package/dist/utils/aigne-hub/store/keytar.cjs +61 -0
  126. package/dist/utils/aigne-hub/store/keytar.mjs +61 -0
  127. package/dist/utils/aigne-hub/store/keytar.mjs.map +1 -0
  128. package/dist/utils/aigne-hub/store/migrate.cjs +46 -0
  129. package/dist/utils/aigne-hub/store/migrate.mjs +45 -0
  130. package/dist/utils/aigne-hub/store/migrate.mjs.map +1 -0
  131. package/dist/utils/aigne-hub/type.d.cts +18 -0
  132. package/dist/utils/aigne-hub/type.d.cts.map +1 -0
  133. package/dist/utils/aigne-hub/type.d.mts +18 -0
  134. package/dist/utils/aigne-hub/type.d.mts.map +1 -0
  135. package/dist/utils/aigne-hub-user.cjs +11 -0
  136. package/dist/utils/aigne-hub-user.d.cts +23 -0
  137. package/dist/utils/aigne-hub-user.d.cts.map +1 -0
  138. package/dist/utils/aigne-hub-user.d.mts +23 -0
  139. package/dist/utils/aigne-hub-user.d.mts.map +1 -0
  140. package/dist/utils/aigne-hub-user.mjs +11 -0
  141. package/dist/utils/aigne-hub-user.mjs.map +1 -0
  142. package/dist/utils/ascii-logo.cjs +30 -0
  143. package/dist/utils/ascii-logo.d.cts +5 -0
  144. package/dist/utils/ascii-logo.d.cts.map +1 -0
  145. package/dist/utils/ascii-logo.d.mts +5 -0
  146. package/dist/utils/ascii-logo.d.mts.map +1 -0
  147. package/dist/utils/{ascii-logo.js → ascii-logo.mjs} +13 -3
  148. package/dist/utils/ascii-logo.mjs.map +1 -0
  149. package/dist/utils/download.cjs +25 -0
  150. package/dist/utils/download.d.cts +7 -0
  151. package/dist/utils/download.d.cts.map +1 -0
  152. package/dist/utils/download.d.mts +7 -0
  153. package/dist/utils/download.d.mts.map +1 -0
  154. package/dist/utils/download.mjs +25 -0
  155. package/dist/utils/download.mjs.map +1 -0
  156. package/dist/utils/evaluation/core.cjs +84 -0
  157. package/dist/utils/evaluation/core.mjs +84 -0
  158. package/dist/utils/evaluation/core.mjs.map +1 -0
  159. package/dist/utils/evaluation/dataset.cjs +47 -0
  160. package/dist/utils/evaluation/dataset.mjs +46 -0
  161. package/dist/utils/evaluation/dataset.mjs.map +1 -0
  162. package/dist/utils/evaluation/evaluator.cjs +109 -0
  163. package/dist/utils/evaluation/{evaluator.js → evaluator.mjs} +48 -45
  164. package/dist/utils/evaluation/evaluator.mjs.map +1 -0
  165. package/dist/utils/evaluation/reporter.cjs +225 -0
  166. package/dist/utils/evaluation/reporter.mjs +220 -0
  167. package/dist/utils/evaluation/reporter.mjs.map +1 -0
  168. package/dist/utils/evaluation/runner.cjs +85 -0
  169. package/dist/utils/evaluation/runner.mjs +85 -0
  170. package/dist/utils/evaluation/runner.mjs.map +1 -0
  171. package/dist/utils/get-url-origin.cjs +12 -0
  172. package/dist/utils/get-url-origin.d.cts +5 -0
  173. package/dist/utils/get-url-origin.d.cts.map +1 -0
  174. package/dist/utils/get-url-origin.d.mts +5 -0
  175. package/dist/utils/get-url-origin.d.mts.map +1 -0
  176. package/dist/utils/get-url-origin.mjs +12 -0
  177. package/dist/utils/get-url-origin.mjs.map +1 -0
  178. package/dist/utils/inquirer/checkbox.cjs +265 -0
  179. package/dist/utils/inquirer/checkbox.mjs +262 -0
  180. package/dist/utils/inquirer/checkbox.mjs.map +1 -0
  181. package/dist/utils/listr.cjs +226 -0
  182. package/dist/utils/listr.d.cts +71 -0
  183. package/dist/utils/listr.d.cts.map +1 -0
  184. package/dist/utils/listr.d.mts +71 -0
  185. package/dist/utils/listr.d.mts.map +1 -0
  186. package/dist/utils/listr.mjs +222 -0
  187. package/dist/utils/listr.mjs.map +1 -0
  188. package/dist/utils/load-aigne.cjs +77 -0
  189. package/dist/utils/load-aigne.d.cts +29 -0
  190. package/dist/utils/load-aigne.d.cts.map +1 -0
  191. package/dist/utils/load-aigne.d.mts +29 -0
  192. package/dist/utils/load-aigne.d.mts.map +1 -0
  193. package/dist/utils/load-aigne.mjs +74 -0
  194. package/dist/utils/load-aigne.mjs.map +1 -0
  195. package/dist/utils/run-chat-loop.cjs +90 -0
  196. package/dist/utils/run-chat-loop.d.cts +20 -0
  197. package/dist/utils/run-chat-loop.d.cts.map +1 -0
  198. package/dist/utils/run-chat-loop.d.mts +20 -0
  199. package/dist/utils/run-chat-loop.d.mts.map +1 -0
  200. package/dist/utils/run-chat-loop.mjs +89 -0
  201. package/dist/utils/run-chat-loop.mjs.map +1 -0
  202. package/dist/utils/run-with-aigne.cjs +131 -0
  203. package/dist/utils/run-with-aigne.d.cts +46 -0
  204. package/dist/utils/run-with-aigne.d.cts.map +1 -0
  205. package/dist/utils/run-with-aigne.d.mts +46 -0
  206. package/dist/utils/run-with-aigne.d.mts.map +1 -0
  207. package/dist/utils/run-with-aigne.mjs +126 -0
  208. package/dist/utils/run-with-aigne.mjs.map +1 -0
  209. package/dist/utils/serve-mcp.cjs +91 -0
  210. package/dist/utils/serve-mcp.d.cts +20 -0
  211. package/dist/utils/serve-mcp.d.cts.map +1 -0
  212. package/dist/utils/serve-mcp.d.mts +20 -0
  213. package/dist/utils/serve-mcp.d.mts.map +1 -0
  214. package/dist/utils/serve-mcp.mjs +89 -0
  215. package/dist/utils/serve-mcp.mjs.map +1 -0
  216. package/dist/utils/spinner.cjs +19 -0
  217. package/dist/utils/spinner.d.cts +5 -0
  218. package/dist/utils/spinner.d.cts.map +1 -0
  219. package/dist/utils/spinner.d.mts +5 -0
  220. package/dist/utils/spinner.d.mts.map +1 -0
  221. package/dist/utils/spinner.mjs +19 -0
  222. package/dist/utils/spinner.mjs.map +1 -0
  223. package/dist/utils/string-utils.cjs +11 -0
  224. package/dist/utils/string-utils.d.cts +5 -0
  225. package/dist/utils/string-utils.d.cts.map +1 -0
  226. package/dist/utils/string-utils.d.mts +5 -0
  227. package/dist/utils/string-utils.d.mts.map +1 -0
  228. package/dist/utils/string-utils.mjs +10 -0
  229. package/dist/utils/string-utils.mjs.map +1 -0
  230. package/dist/utils/time.cjs +14 -0
  231. package/dist/utils/time.d.cts +5 -0
  232. package/dist/utils/time.d.cts.map +1 -0
  233. package/dist/utils/time.d.mts +5 -0
  234. package/dist/utils/time.d.mts.map +1 -0
  235. package/dist/utils/time.mjs +14 -0
  236. package/dist/utils/time.mjs.map +1 -0
  237. package/dist/utils/url.cjs +8 -0
  238. package/dist/utils/url.d.cts +5 -0
  239. package/dist/utils/url.d.cts.map +1 -0
  240. package/dist/utils/url.d.mts +5 -0
  241. package/dist/utils/url.d.mts.map +1 -0
  242. package/dist/utils/url.mjs +8 -0
  243. package/dist/utils/url.mjs.map +1 -0
  244. package/dist/utils/yargs.cjs +191 -0
  245. package/dist/utils/yargs.d.cts +96 -0
  246. package/dist/utils/yargs.d.cts.map +1 -0
  247. package/dist/utils/yargs.d.mts +96 -0
  248. package/dist/utils/yargs.d.mts.map +1 -0
  249. package/dist/utils/yargs.mjs +186 -0
  250. package/dist/utils/yargs.mjs.map +1 -0
  251. package/package.json +122 -45
  252. package/CHANGELOG.md +0 -5019
  253. package/dist/bunwrapper.d.ts +0 -2
  254. package/dist/bunwrapper.js +0 -18
  255. package/dist/cli.d.ts +0 -7
  256. package/dist/cli.js +0 -42
  257. package/dist/commands/aigne.d.ts +0 -4
  258. package/dist/commands/aigne.js +0 -35
  259. package/dist/commands/app/agent.d.ts +0 -26
  260. package/dist/commands/app/agent.js +0 -122
  261. package/dist/commands/app/app.d.ts +0 -7
  262. package/dist/commands/app/app.js +0 -92
  263. package/dist/commands/app/cli.d.ts +0 -1
  264. package/dist/commands/app/cli.js +0 -2
  265. package/dist/commands/app/upgrade.d.ts +0 -54
  266. package/dist/commands/app/upgrade.js +0 -236
  267. package/dist/commands/app.d.ts +0 -4
  268. package/dist/commands/app.js +0 -54
  269. package/dist/commands/create.d.ts +0 -6
  270. package/dist/commands/create.js +0 -74
  271. package/dist/commands/deploy.d.ts +0 -11
  272. package/dist/commands/deploy.js +0 -255
  273. package/dist/commands/eval.d.ts +0 -11
  274. package/dist/commands/eval.js +0 -110
  275. package/dist/commands/hub.d.ts +0 -3
  276. package/dist/commands/hub.js +0 -323
  277. package/dist/commands/observe.d.ts +0 -7
  278. package/dist/commands/observe.js +0 -41
  279. package/dist/commands/run-skill.d.ts +0 -6
  280. package/dist/commands/run-skill.js +0 -102
  281. package/dist/commands/run.d.ts +0 -9
  282. package/dist/commands/run.js +0 -187
  283. package/dist/commands/serve-mcp.d.ts +0 -20
  284. package/dist/commands/serve-mcp.js +0 -67
  285. package/dist/commands/test.d.ts +0 -9
  286. package/dist/commands/test.js +0 -33
  287. package/dist/constants.d.ts +0 -7
  288. package/dist/constants.js +0 -21
  289. package/dist/index.d.ts +0 -1
  290. package/dist/index.js +0 -1
  291. package/dist/tracer/terminal.d.ts +0 -62
  292. package/dist/tracer/terminal.js +0 -404
  293. package/dist/type.d.ts +0 -5
  294. package/dist/type.js +0 -1
  295. package/dist/ui/utils/terminal-input.d.ts +0 -19
  296. package/dist/ui/utils/terminal-input.js +0 -123
  297. package/dist/ui/utils/text-buffer.d.ts +0 -87
  298. package/dist/ui/utils/text-buffer.js +0 -1059
  299. package/dist/ui/utils/text-utils.d.ts +0 -37
  300. package/dist/ui/utils/text-utils.js +0 -185
  301. package/dist/utils/agent-v1.d.ts +0 -134
  302. package/dist/utils/agent-v1.js +0 -213
  303. package/dist/utils/aigne-hub/constants.d.ts +0 -6
  304. package/dist/utils/aigne-hub/constants.js +0 -12
  305. package/dist/utils/aigne-hub/credential.d.ts +0 -20
  306. package/dist/utils/aigne-hub/credential.js +0 -182
  307. package/dist/utils/aigne-hub/crypto.d.ts +0 -4
  308. package/dist/utils/aigne-hub/crypto.js +0 -30
  309. package/dist/utils/aigne-hub/model.d.ts +0 -13
  310. package/dist/utils/aigne-hub/model.js +0 -122
  311. package/dist/utils/aigne-hub/store/file.d.ts +0 -15
  312. package/dist/utils/aigne-hub/store/file.js +0 -69
  313. package/dist/utils/aigne-hub/store/index.d.ts +0 -5
  314. package/dist/utils/aigne-hub/store/index.js +0 -43
  315. package/dist/utils/aigne-hub/store/keytar.d.ts +0 -15
  316. package/dist/utils/aigne-hub/store/keytar.js +0 -67
  317. package/dist/utils/aigne-hub/store/migrate.d.ts +0 -2
  318. package/dist/utils/aigne-hub/store/migrate.js +0 -57
  319. package/dist/utils/aigne-hub/type.d.ts +0 -38
  320. package/dist/utils/aigne-hub/type.js +0 -1
  321. package/dist/utils/aigne-hub-user.d.ts +0 -16
  322. package/dist/utils/aigne-hub-user.js +0 -10
  323. package/dist/utils/ascii-logo.d.ts +0 -1
  324. package/dist/utils/download.d.ts +0 -3
  325. package/dist/utils/download.js +0 -19
  326. package/dist/utils/evaluation/core.d.ts +0 -8
  327. package/dist/utils/evaluation/core.js +0 -83
  328. package/dist/utils/evaluation/dataset.d.ts +0 -15
  329. package/dist/utils/evaluation/dataset.js +0 -61
  330. package/dist/utils/evaluation/evaluator.d.ts +0 -9
  331. package/dist/utils/evaluation/reporter.d.ts +0 -28
  332. package/dist/utils/evaluation/reporter.js +0 -221
  333. package/dist/utils/evaluation/runner.d.ts +0 -16
  334. package/dist/utils/evaluation/runner.js +0 -129
  335. package/dist/utils/evaluation/type.d.ts +0 -69
  336. package/dist/utils/evaluation/type.js +0 -1
  337. package/dist/utils/get-url-origin.d.ts +0 -1
  338. package/dist/utils/get-url-origin.js +0 -8
  339. package/dist/utils/inquirer/checkbox.d.ts +0 -55
  340. package/dist/utils/inquirer/checkbox.js +0 -319
  341. package/dist/utils/listr.d.ts +0 -64
  342. package/dist/utils/listr.js +0 -265
  343. package/dist/utils/load-aigne.d.ts +0 -18
  344. package/dist/utils/load-aigne.js +0 -80
  345. package/dist/utils/run-chat-loop.d.ts +0 -15
  346. package/dist/utils/run-chat-loop.js +0 -87
  347. package/dist/utils/run-with-aigne.d.ts +0 -27
  348. package/dist/utils/run-with-aigne.js +0 -157
  349. package/dist/utils/serve-mcp.d.ts +0 -9
  350. package/dist/utils/serve-mcp.js +0 -93
  351. package/dist/utils/spinner.d.ts +0 -1
  352. package/dist/utils/spinner.js +0 -14
  353. package/dist/utils/string-utils.d.ts +0 -1
  354. package/dist/utils/string-utils.js +0 -4
  355. package/dist/utils/time.d.ts +0 -1
  356. package/dist/utils/time.js +0 -12
  357. package/dist/utils/url.d.ts +0 -1
  358. package/dist/utils/url.js +0 -3
  359. package/dist/utils/yargs.d.ts +0 -94
  360. package/dist/utils/yargs.js +0 -210
@@ -1,1059 +0,0 @@
1
- // biome-ignore-all lint/style/noNonNullAssertion: code is from gemini-cli
2
- // biome-ignore-all lint/correctness/useExhaustiveDependencies: code is from gemini-cli
3
- /**
4
- * @license
5
- * Copyright 2025 Google LLC
6
- * SPDX-License-Identifier: Apache-2.0
7
- */
8
- import { useCallback, useEffect, useMemo, useReducer, useState } from "react";
9
- import { cpLen, cpSlice, getCachedStringWidth, stripUnsafeCharacters, toCodePoints, } from "./text-utils.js";
10
- // Simple helper for word‑wise ops.
11
- function isWordChar(ch) {
12
- if (ch === undefined) {
13
- return false;
14
- }
15
- return !/[\s,.;!?]/.test(ch);
16
- }
17
- // Helper functions for line-based word navigation
18
- const isWordCharStrict = (char) => /[\w\p{L}\p{N}]/u.test(char);
19
- const isWhitespace = (char) => /\s/.test(char);
20
- const isCombiningMark = (char) => /\p{M}/u.test(char);
21
- const isWordCharWithCombining = (char) => isWordCharStrict(char) || isCombiningMark(char);
22
- const getCharScript = (char) => {
23
- if (/[\p{Script=Latin}]/u.test(char))
24
- return "latin";
25
- if (/[\p{Script=Han}]/u.test(char))
26
- return "han";
27
- if (/[\p{Script=Arabic}]/u.test(char))
28
- return "arabic";
29
- if (/[\p{Script=Hiragana}]/u.test(char))
30
- return "hiragana";
31
- if (/[\p{Script=Katakana}]/u.test(char))
32
- return "katakana";
33
- if (/[\p{Script=Cyrillic}]/u.test(char))
34
- return "cyrillic";
35
- return "other";
36
- };
37
- const isDifferentScript = (char1, char2) => {
38
- if (!isWordCharStrict(char1) || !isWordCharStrict(char2))
39
- return false;
40
- return getCharScript(char1) !== getCharScript(char2);
41
- };
42
- const findNextWordStartInLine = (line, col) => {
43
- const chars = toCodePoints(line);
44
- let i = col;
45
- if (i >= chars.length)
46
- return null;
47
- const currentChar = chars[i];
48
- // Skip current word/sequence based on character type
49
- if (isWordCharStrict(currentChar)) {
50
- while (i < chars.length && isWordCharWithCombining(chars[i])) {
51
- // Check for script boundary - if next character is from different script, stop here
52
- if (i + 1 < chars.length &&
53
- isWordCharStrict(chars[i + 1]) &&
54
- isDifferentScript(chars[i], chars[i + 1])) {
55
- i++; // Include current character
56
- break; // Stop at script boundary
57
- }
58
- i++;
59
- }
60
- }
61
- else if (!isWhitespace(currentChar)) {
62
- while (i < chars.length && !isWordCharStrict(chars[i]) && !isWhitespace(chars[i])) {
63
- i++;
64
- }
65
- }
66
- // Skip whitespace
67
- while (i < chars.length && isWhitespace(chars[i])) {
68
- i++;
69
- }
70
- return i < chars.length ? i : null;
71
- };
72
- // Find previous word start within a line
73
- const findPrevWordStartInLine = (line, col) => {
74
- const chars = toCodePoints(line);
75
- let i = col;
76
- if (i <= 0)
77
- return null;
78
- i--;
79
- // Skip whitespace moving backwards
80
- while (i >= 0 && isWhitespace(chars[i])) {
81
- i--;
82
- }
83
- if (i < 0)
84
- return null;
85
- if (isWordCharStrict(chars[i])) {
86
- // We're in a word, move to its beginning
87
- while (i >= 0 && isWordCharStrict(chars[i])) {
88
- // Check for script boundary - if previous character is from different script, stop here
89
- if (i - 1 >= 0 &&
90
- isWordCharStrict(chars[i - 1]) &&
91
- isDifferentScript(chars[i], chars[i - 1])) {
92
- return i; // Return current position at script boundary
93
- }
94
- i--;
95
- }
96
- return i + 1;
97
- }
98
- else {
99
- // We're in punctuation, move to its beginning
100
- while (i >= 0 && !isWordCharStrict(chars[i]) && !isWhitespace(chars[i])) {
101
- i--;
102
- }
103
- return i + 1;
104
- }
105
- };
106
- // Find word end within a line
107
- // Find next word across lines
108
- // Find previous word across lines
109
- // Helper functions for vim line operations
110
- const replaceRangeInternal = (state, startRow, startCol, endRow, endCol, text) => {
111
- const currentLine = (row) => state.lines[row] || "";
112
- const currentLineLen = (row) => cpLen(currentLine(row));
113
- const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
114
- if (startRow > endRow ||
115
- (startRow === endRow && startCol > endCol) ||
116
- startRow < 0 ||
117
- startCol < 0 ||
118
- endRow >= state.lines.length ||
119
- (endRow < state.lines.length && endCol > currentLineLen(endRow))) {
120
- return state; // Invalid range
121
- }
122
- const newLines = [...state.lines];
123
- const sCol = clamp(startCol, 0, currentLineLen(startRow));
124
- const eCol = clamp(endCol, 0, currentLineLen(endRow));
125
- const prefix = cpSlice(currentLine(startRow), 0, sCol);
126
- const suffix = cpSlice(currentLine(endRow), eCol);
127
- const normalisedReplacement = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
128
- const replacementParts = normalisedReplacement.split("\n");
129
- // The combined first line of the new text
130
- const firstLine = prefix + replacementParts[0];
131
- if (replacementParts.length === 1) {
132
- // No newlines in replacement: combine prefix, replacement, and suffix on one line.
133
- newLines.splice(startRow, endRow - startRow + 1, firstLine + suffix);
134
- }
135
- else {
136
- // Newlines in replacement: create new lines.
137
- const lastLine = replacementParts[replacementParts.length - 1] + suffix;
138
- const middleLines = replacementParts.slice(1, -1);
139
- newLines.splice(startRow, endRow - startRow + 1, firstLine, ...middleLines, lastLine);
140
- }
141
- const finalCursorRow = startRow + replacementParts.length - 1;
142
- const finalCursorCol = (replacementParts.length > 1 ? 0 : sCol) +
143
- cpLen(replacementParts[replacementParts.length - 1]);
144
- return {
145
- ...state,
146
- lines: newLines,
147
- cursorRow: Math.min(Math.max(finalCursorRow, 0), newLines.length - 1),
148
- cursorCol: Math.max(0, Math.min(finalCursorCol, cpLen(newLines[finalCursorRow] || ""))),
149
- preferredCol: null,
150
- };
151
- };
152
- function clamp(v, min, max) {
153
- return v < min ? min : v > max ? max : v;
154
- }
155
- function calculateInitialCursorPosition(initialLines, offset) {
156
- let remainingChars = offset;
157
- let row = 0;
158
- while (row < initialLines.length) {
159
- const lineLength = cpLen(initialLines[row]);
160
- // Add 1 for the newline character (except for the last line)
161
- const totalCharsInLineAndNewline = lineLength + (row < initialLines.length - 1 ? 1 : 0);
162
- if (remainingChars <= lineLength) {
163
- // Cursor is on this line
164
- return [row, remainingChars];
165
- }
166
- remainingChars -= totalCharsInLineAndNewline;
167
- row++;
168
- }
169
- // Offset is beyond the text, place cursor at the end of the last line
170
- if (initialLines.length > 0) {
171
- const lastRow = initialLines.length - 1;
172
- return [lastRow, cpLen(initialLines[lastRow])];
173
- }
174
- return [0, 0]; // Default for empty text
175
- }
176
- function offsetToLogicalPos(text, offset) {
177
- let row = 0;
178
- let col = 0;
179
- let currentOffset = 0;
180
- if (offset === 0)
181
- return [0, 0];
182
- const lines = text.split("\n");
183
- for (let i = 0; i < lines.length; i++) {
184
- const line = lines[i];
185
- const lineLength = cpLen(line);
186
- const lineLengthWithNewline = lineLength + (i < lines.length - 1 ? 1 : 0);
187
- if (offset <= currentOffset + lineLength) {
188
- // Check against lineLength first
189
- row = i;
190
- col = offset - currentOffset;
191
- return [row, col];
192
- }
193
- else if (offset <= currentOffset + lineLengthWithNewline) {
194
- // Check if offset is the newline itself
195
- row = i;
196
- col = lineLength; // Position cursor at the end of the current line content
197
- // If the offset IS the newline, and it's not the last line, advance to next line, col 0
198
- if (offset === currentOffset + lineLengthWithNewline && i < lines.length - 1) {
199
- return [i + 1, 0];
200
- }
201
- return [row, col]; // Otherwise, it's at the end of the current line content
202
- }
203
- currentOffset += lineLengthWithNewline;
204
- }
205
- // If offset is beyond the text length, place cursor at the end of the last line
206
- // or [0,0] if text is empty
207
- if (lines.length > 0) {
208
- row = lines.length - 1;
209
- col = cpLen(lines[row]);
210
- }
211
- else {
212
- row = 0;
213
- col = 0;
214
- }
215
- return [row, col];
216
- }
217
- // Calculates the visual wrapping of lines and the mapping between logical and visual coordinates.
218
- // This is an expensive operation and should be memoized.
219
- function calculateLayout(logicalLines, viewportWidth) {
220
- const visualLines = [];
221
- const logicalToVisualMap = [];
222
- const visualToLogicalMap = [];
223
- logicalLines.forEach((logLine, logIndex) => {
224
- logicalToVisualMap[logIndex] = [];
225
- if (logLine.length === 0) {
226
- // Handle empty logical line
227
- logicalToVisualMap[logIndex].push([visualLines.length, 0]);
228
- visualToLogicalMap.push([logIndex, 0]);
229
- visualLines.push("");
230
- }
231
- else {
232
- // Non-empty logical line
233
- let currentPosInLogLine = 0; // Tracks position within the current logical line (code point index)
234
- const codePointsInLogLine = toCodePoints(logLine);
235
- while (currentPosInLogLine < codePointsInLogLine.length) {
236
- let currentChunk = "";
237
- let currentChunkVisualWidth = 0;
238
- let numCodePointsInChunk = 0;
239
- let lastWordBreakPoint = -1; // Index in codePointsInLogLine for word break
240
- let numCodePointsAtLastWordBreak = 0;
241
- // Iterate through code points to build the current visual line (chunk)
242
- for (let i = currentPosInLogLine; i < codePointsInLogLine.length; i++) {
243
- const char = codePointsInLogLine[i];
244
- const charVisualWidth = getCachedStringWidth(char);
245
- if (currentChunkVisualWidth + charVisualWidth > viewportWidth) {
246
- // Character would exceed viewport width
247
- if (lastWordBreakPoint !== -1 &&
248
- numCodePointsAtLastWordBreak > 0 &&
249
- currentPosInLogLine + numCodePointsAtLastWordBreak < i) {
250
- // We have a valid word break point to use, and it's not the start of the current segment
251
- currentChunk = codePointsInLogLine
252
- .slice(currentPosInLogLine, currentPosInLogLine + numCodePointsAtLastWordBreak)
253
- .join("");
254
- numCodePointsInChunk = numCodePointsAtLastWordBreak;
255
- }
256
- else {
257
- // No word break, or word break is at the start of this potential chunk, or word break leads to empty chunk.
258
- // Hard break: take characters up to viewportWidth, or just the current char if it alone is too wide.
259
- if (numCodePointsInChunk === 0 && charVisualWidth > viewportWidth) {
260
- // Single character is wider than viewport, take it anyway
261
- currentChunk = char;
262
- numCodePointsInChunk = 1;
263
- }
264
- else if (numCodePointsInChunk === 0 && charVisualWidth <= viewportWidth) {
265
- // This case should ideally be caught by the next iteration if the char fits.
266
- // If it doesn't fit (because currentChunkVisualWidth was already > 0 from a previous char that filled the line),
267
- // then numCodePointsInChunk would not be 0.
268
- // This branch means the current char *itself* doesn't fit an empty line, which is handled by the above.
269
- // If we are here, it means the loop should break and the current chunk (which is empty) is finalized.
270
- }
271
- }
272
- break; // Break from inner loop to finalize this chunk
273
- }
274
- currentChunk += char;
275
- currentChunkVisualWidth += charVisualWidth;
276
- numCodePointsInChunk++;
277
- // Check for word break opportunity (space)
278
- if (char === " ") {
279
- lastWordBreakPoint = i; // Store code point index of the space
280
- // Store the state *before* adding the space, if we decide to break here.
281
- numCodePointsAtLastWordBreak = numCodePointsInChunk - 1; // Chars *before* the space
282
- }
283
- }
284
- // If the inner loop completed without breaking (i.e., remaining text fits)
285
- // or if the loop broke but numCodePointsInChunk is still 0 (e.g. first char too wide for empty line)
286
- if (numCodePointsInChunk === 0 && currentPosInLogLine < codePointsInLogLine.length) {
287
- // This can happen if the very first character considered for a new visual line is wider than the viewport.
288
- // In this case, we take that single character.
289
- const firstChar = codePointsInLogLine[currentPosInLogLine];
290
- currentChunk = firstChar;
291
- numCodePointsInChunk = 1; // Ensure we advance
292
- }
293
- // If after everything, numCodePointsInChunk is still 0 but we haven't processed the whole logical line,
294
- // it implies an issue, like viewportWidth being 0 or less. Avoid infinite loop.
295
- if (numCodePointsInChunk === 0 && currentPosInLogLine < codePointsInLogLine.length) {
296
- // Force advance by one character to prevent infinite loop if something went wrong
297
- currentChunk = codePointsInLogLine[currentPosInLogLine];
298
- numCodePointsInChunk = 1;
299
- }
300
- logicalToVisualMap[logIndex].push([visualLines.length, currentPosInLogLine]);
301
- visualToLogicalMap.push([logIndex, currentPosInLogLine]);
302
- visualLines.push(currentChunk);
303
- const logicalStartOfThisChunk = currentPosInLogLine;
304
- currentPosInLogLine += numCodePointsInChunk;
305
- // If the chunk processed did not consume the entire logical line,
306
- // and the character immediately following the chunk is a space,
307
- // advance past this space as it acted as a delimiter for word wrapping.
308
- if (logicalStartOfThisChunk + numCodePointsInChunk < codePointsInLogLine.length &&
309
- currentPosInLogLine < codePointsInLogLine.length && // Redundant if previous is true, but safe
310
- codePointsInLogLine[currentPosInLogLine] === " ") {
311
- currentPosInLogLine++;
312
- }
313
- }
314
- }
315
- });
316
- // If the entire logical text was empty, ensure there's one empty visual line.
317
- if (logicalLines.length === 0 || (logicalLines.length === 1 && logicalLines[0] === "")) {
318
- if (visualLines.length === 0) {
319
- visualLines.push("");
320
- if (!logicalToVisualMap[0])
321
- logicalToVisualMap[0] = [];
322
- logicalToVisualMap[0].push([0, 0]);
323
- visualToLogicalMap.push([0, 0]);
324
- }
325
- }
326
- return {
327
- visualLines,
328
- logicalToVisualMap,
329
- visualToLogicalMap,
330
- };
331
- }
332
- // Calculates the visual cursor position based on a pre-calculated layout.
333
- // This is a lightweight operation.
334
- function calculateVisualCursorFromLayout(layout, logicalCursor) {
335
- const { logicalToVisualMap, visualLines } = layout;
336
- const [logicalRow, logicalCol] = logicalCursor;
337
- const segmentsForLogicalLine = logicalToVisualMap[logicalRow];
338
- if (!segmentsForLogicalLine || segmentsForLogicalLine.length === 0) {
339
- // This can happen for an empty document.
340
- return [0, 0];
341
- }
342
- // Find the segment where the logical column fits.
343
- // The segments are sorted by startColInLogical.
344
- let targetSegmentIndex = segmentsForLogicalLine.findIndex(([, startColInLogical], index) => {
345
- const nextStartColInLogical = index + 1 < segmentsForLogicalLine.length ? segmentsForLogicalLine[index + 1][1] : Infinity;
346
- return logicalCol >= startColInLogical && logicalCol < nextStartColInLogical;
347
- });
348
- // If not found, it means the cursor is at the end of the logical line.
349
- if (targetSegmentIndex === -1) {
350
- if (logicalCol === 0) {
351
- targetSegmentIndex = 0;
352
- }
353
- else {
354
- targetSegmentIndex = segmentsForLogicalLine.length - 1;
355
- }
356
- }
357
- const [visualRow, startColInLogical] = segmentsForLogicalLine[targetSegmentIndex];
358
- const visualCol = logicalCol - startColInLogical;
359
- // The visual column should not exceed the length of the visual line.
360
- const clampedVisualCol = Math.min(visualCol, cpLen(visualLines[visualRow] ?? ""));
361
- return [visualRow, clampedVisualCol];
362
- }
363
- const historyLimit = 100;
364
- const pushUndo = (currentState) => {
365
- const snapshot = {
366
- lines: [...currentState.lines],
367
- cursorRow: currentState.cursorRow,
368
- cursorCol: currentState.cursorCol,
369
- };
370
- const newStack = [...currentState.undoStack, snapshot];
371
- if (newStack.length > historyLimit) {
372
- newStack.shift();
373
- }
374
- return { ...currentState, undoStack: newStack, redoStack: [] };
375
- };
376
- function textBufferReducerLogic(state, action) {
377
- const pushUndoLocal = pushUndo;
378
- const currentLine = (r) => state.lines[r] ?? "";
379
- const currentLineLen = (r) => cpLen(currentLine(r));
380
- switch (action.type) {
381
- case "set_text": {
382
- let nextState = state;
383
- if (action.pushToUndo !== false) {
384
- nextState = pushUndoLocal(state);
385
- }
386
- const newContentLines = action.payload.replace(/\r\n?/g, "\n").split("\n");
387
- const lines = newContentLines.length === 0 ? [""] : newContentLines;
388
- const lastNewLineIndex = lines.length - 1;
389
- return {
390
- ...nextState,
391
- lines,
392
- cursorRow: lastNewLineIndex,
393
- cursorCol: cpLen(lines[lastNewLineIndex] ?? ""),
394
- preferredCol: null,
395
- };
396
- }
397
- case "insert": {
398
- const nextState = pushUndoLocal(state);
399
- const newLines = [...nextState.lines];
400
- let newCursorRow = nextState.cursorRow;
401
- let newCursorCol = nextState.cursorCol;
402
- const currentLine = (r) => newLines[r] ?? "";
403
- const str = stripUnsafeCharacters(action.payload.replace(/\r\n/g, "\n").replace(/\r/g, "\n"));
404
- const parts = str.split("\n");
405
- const lineContent = currentLine(newCursorRow);
406
- const before = cpSlice(lineContent, 0, newCursorCol);
407
- const after = cpSlice(lineContent, newCursorCol);
408
- if (parts.length > 1) {
409
- newLines[newCursorRow] = before + parts[0];
410
- const remainingParts = parts.slice(1);
411
- const lastPartOriginal = remainingParts.pop() ?? "";
412
- newLines.splice(newCursorRow + 1, 0, ...remainingParts);
413
- newLines.splice(newCursorRow + parts.length - 1, 0, lastPartOriginal + after);
414
- newCursorRow = newCursorRow + parts.length - 1;
415
- newCursorCol = cpLen(lastPartOriginal);
416
- }
417
- else {
418
- newLines[newCursorRow] = before + parts[0] + after;
419
- newCursorCol = cpLen(before) + cpLen(parts[0]);
420
- }
421
- return {
422
- ...nextState,
423
- lines: newLines,
424
- cursorRow: newCursorRow,
425
- cursorCol: newCursorCol,
426
- preferredCol: null,
427
- };
428
- }
429
- case "backspace": {
430
- const nextState = pushUndoLocal(state);
431
- const newLines = [...nextState.lines];
432
- let newCursorRow = nextState.cursorRow;
433
- let newCursorCol = nextState.cursorCol;
434
- const currentLine = (r) => newLines[r] ?? "";
435
- if (newCursorCol === 0 && newCursorRow === 0)
436
- return state;
437
- if (newCursorCol > 0) {
438
- const lineContent = currentLine(newCursorRow);
439
- newLines[newCursorRow] =
440
- cpSlice(lineContent, 0, newCursorCol - 1) + cpSlice(lineContent, newCursorCol);
441
- newCursorCol--;
442
- }
443
- else if (newCursorRow > 0) {
444
- const prevLineContent = currentLine(newCursorRow - 1);
445
- const currentLineContentVal = currentLine(newCursorRow);
446
- const newCol = cpLen(prevLineContent);
447
- newLines[newCursorRow - 1] = prevLineContent + currentLineContentVal;
448
- newLines.splice(newCursorRow, 1);
449
- newCursorRow--;
450
- newCursorCol = newCol;
451
- }
452
- return {
453
- ...nextState,
454
- lines: newLines,
455
- cursorRow: newCursorRow,
456
- cursorCol: newCursorCol,
457
- preferredCol: null,
458
- };
459
- }
460
- case "set_viewport": {
461
- const { width, height } = action.payload;
462
- if (width === state.viewportWidth && height === state.viewportHeight) {
463
- return state;
464
- }
465
- return {
466
- ...state,
467
- viewportWidth: width,
468
- viewportHeight: height,
469
- };
470
- }
471
- case "move": {
472
- const { dir } = action.payload;
473
- const { cursorRow, cursorCol, lines, visualLayout, preferredCol } = state;
474
- // Visual movements
475
- if (dir === "left" ||
476
- dir === "right" ||
477
- dir === "up" ||
478
- dir === "down" ||
479
- dir === "home" ||
480
- dir === "end") {
481
- const visualCursor = calculateVisualCursorFromLayout(visualLayout, [cursorRow, cursorCol]);
482
- const { visualLines, visualToLogicalMap } = visualLayout;
483
- let newVisualRow = visualCursor[0];
484
- let newVisualCol = visualCursor[1];
485
- let newPreferredCol = preferredCol;
486
- const currentVisLineLen = cpLen(visualLines[newVisualRow] ?? "");
487
- switch (dir) {
488
- case "left":
489
- newPreferredCol = null;
490
- if (newVisualCol > 0) {
491
- newVisualCol--;
492
- }
493
- else if (newVisualRow > 0) {
494
- newVisualRow--;
495
- newVisualCol = cpLen(visualLines[newVisualRow] ?? "");
496
- }
497
- break;
498
- case "right":
499
- newPreferredCol = null;
500
- if (newVisualCol < currentVisLineLen) {
501
- newVisualCol++;
502
- }
503
- else if (newVisualRow < visualLines.length - 1) {
504
- newVisualRow++;
505
- newVisualCol = 0;
506
- }
507
- break;
508
- case "up":
509
- if (newVisualRow > 0) {
510
- if (newPreferredCol === null)
511
- newPreferredCol = newVisualCol;
512
- newVisualRow--;
513
- newVisualCol = clamp(newPreferredCol, 0, cpLen(visualLines[newVisualRow] ?? ""));
514
- }
515
- break;
516
- case "down":
517
- if (newVisualRow < visualLines.length - 1) {
518
- if (newPreferredCol === null)
519
- newPreferredCol = newVisualCol;
520
- newVisualRow++;
521
- newVisualCol = clamp(newPreferredCol, 0, cpLen(visualLines[newVisualRow] ?? ""));
522
- }
523
- break;
524
- case "home":
525
- newPreferredCol = null;
526
- newVisualCol = 0;
527
- break;
528
- case "end":
529
- newPreferredCol = null;
530
- newVisualCol = currentVisLineLen;
531
- break;
532
- default: {
533
- const exhaustiveCheck = dir;
534
- console.error(`Unknown visual movement direction: ${exhaustiveCheck}`);
535
- return state;
536
- }
537
- }
538
- if (visualToLogicalMap[newVisualRow]) {
539
- const [logRow, logStartCol] = visualToLogicalMap[newVisualRow];
540
- return {
541
- ...state,
542
- cursorRow: logRow,
543
- cursorCol: clamp(logStartCol + newVisualCol, 0, cpLen(lines[logRow] ?? "")),
544
- preferredCol: newPreferredCol,
545
- };
546
- }
547
- return state;
548
- }
549
- // Logical movements
550
- switch (dir) {
551
- case "wordLeft": {
552
- if (cursorCol === 0 && cursorRow === 0)
553
- return state;
554
- let newCursorRow = cursorRow;
555
- let newCursorCol = cursorCol;
556
- if (cursorCol === 0) {
557
- newCursorRow--;
558
- newCursorCol = cpLen(lines[newCursorRow] ?? "");
559
- }
560
- else {
561
- const lineContent = lines[cursorRow];
562
- const arr = toCodePoints(lineContent);
563
- let start = cursorCol;
564
- let onlySpaces = true;
565
- for (let i = 0; i < start; i++) {
566
- if (isWordChar(arr[i])) {
567
- onlySpaces = false;
568
- break;
569
- }
570
- }
571
- if (onlySpaces && start > 0) {
572
- start--;
573
- }
574
- else {
575
- while (start > 0 && !isWordChar(arr[start - 1]))
576
- start--;
577
- while (start > 0 && isWordChar(arr[start - 1]))
578
- start--;
579
- }
580
- newCursorCol = start;
581
- }
582
- return {
583
- ...state,
584
- cursorRow: newCursorRow,
585
- cursorCol: newCursorCol,
586
- preferredCol: null,
587
- };
588
- }
589
- case "wordRight": {
590
- if (cursorRow === lines.length - 1 && cursorCol === cpLen(lines[cursorRow] ?? "")) {
591
- return state;
592
- }
593
- let newCursorRow = cursorRow;
594
- let newCursorCol = cursorCol;
595
- const lineContent = lines[cursorRow] ?? "";
596
- const arr = toCodePoints(lineContent);
597
- if (cursorCol >= arr.length) {
598
- newCursorRow++;
599
- newCursorCol = 0;
600
- }
601
- else {
602
- let end = cursorCol;
603
- while (end < arr.length && !isWordChar(arr[end]))
604
- end++;
605
- while (end < arr.length && isWordChar(arr[end]))
606
- end++;
607
- newCursorCol = end;
608
- }
609
- return {
610
- ...state,
611
- cursorRow: newCursorRow,
612
- cursorCol: newCursorCol,
613
- preferredCol: null,
614
- };
615
- }
616
- default:
617
- return state;
618
- }
619
- }
620
- case "set_cursor": {
621
- return {
622
- ...state,
623
- ...action.payload,
624
- };
625
- }
626
- case "delete": {
627
- const { cursorRow, cursorCol, lines } = state;
628
- const lineContent = currentLine(cursorRow);
629
- if (cursorCol < currentLineLen(cursorRow)) {
630
- const nextState = pushUndoLocal(state);
631
- const newLines = [...nextState.lines];
632
- newLines[cursorRow] =
633
- cpSlice(lineContent, 0, cursorCol) + cpSlice(lineContent, cursorCol + 1);
634
- return {
635
- ...nextState,
636
- lines: newLines,
637
- preferredCol: null,
638
- };
639
- }
640
- else if (cursorRow < lines.length - 1) {
641
- const nextState = pushUndoLocal(state);
642
- const nextLineContent = currentLine(cursorRow + 1);
643
- const newLines = [...nextState.lines];
644
- newLines[cursorRow] = lineContent + nextLineContent;
645
- newLines.splice(cursorRow + 1, 1);
646
- return {
647
- ...nextState,
648
- lines: newLines,
649
- preferredCol: null,
650
- };
651
- }
652
- return state;
653
- }
654
- case "delete_word_left": {
655
- const { cursorRow, cursorCol } = state;
656
- if (cursorCol === 0 && cursorRow === 0)
657
- return state;
658
- const nextState = pushUndoLocal(state);
659
- const newLines = [...nextState.lines];
660
- let newCursorRow = cursorRow;
661
- let newCursorCol = cursorCol;
662
- if (newCursorCol > 0) {
663
- const lineContent = currentLine(newCursorRow);
664
- const prevWordStart = findPrevWordStartInLine(lineContent, newCursorCol);
665
- const start = prevWordStart === null ? 0 : prevWordStart;
666
- newLines[newCursorRow] =
667
- cpSlice(lineContent, 0, start) + cpSlice(lineContent, newCursorCol);
668
- newCursorCol = start;
669
- }
670
- else {
671
- // Act as a backspace
672
- const prevLineContent = currentLine(cursorRow - 1);
673
- const currentLineContentVal = currentLine(cursorRow);
674
- const newCol = cpLen(prevLineContent);
675
- newLines[cursorRow - 1] = prevLineContent + currentLineContentVal;
676
- newLines.splice(cursorRow, 1);
677
- newCursorRow--;
678
- newCursorCol = newCol;
679
- }
680
- return {
681
- ...nextState,
682
- lines: newLines,
683
- cursorRow: newCursorRow,
684
- cursorCol: newCursorCol,
685
- preferredCol: null,
686
- };
687
- }
688
- case "delete_word_right": {
689
- const { cursorRow, cursorCol, lines } = state;
690
- const lineContent = currentLine(cursorRow);
691
- const lineLen = cpLen(lineContent);
692
- if (cursorCol >= lineLen && cursorRow === lines.length - 1) {
693
- return state;
694
- }
695
- const nextState = pushUndoLocal(state);
696
- const newLines = [...nextState.lines];
697
- if (cursorCol >= lineLen) {
698
- // Act as a delete, joining with the next line
699
- const nextLineContent = currentLine(cursorRow + 1);
700
- newLines[cursorRow] = lineContent + nextLineContent;
701
- newLines.splice(cursorRow + 1, 1);
702
- }
703
- else {
704
- const nextWordStart = findNextWordStartInLine(lineContent, cursorCol);
705
- const end = nextWordStart === null ? lineLen : nextWordStart;
706
- newLines[cursorRow] = cpSlice(lineContent, 0, cursorCol) + cpSlice(lineContent, end);
707
- }
708
- return {
709
- ...nextState,
710
- lines: newLines,
711
- preferredCol: null,
712
- };
713
- }
714
- case "kill_line_right": {
715
- const { cursorRow, cursorCol, lines } = state;
716
- const lineContent = currentLine(cursorRow);
717
- if (cursorCol < currentLineLen(cursorRow)) {
718
- const nextState = pushUndoLocal(state);
719
- const newLines = [...nextState.lines];
720
- newLines[cursorRow] = cpSlice(lineContent, 0, cursorCol);
721
- return {
722
- ...nextState,
723
- lines: newLines,
724
- };
725
- }
726
- else if (cursorRow < lines.length - 1) {
727
- // Act as a delete
728
- const nextState = pushUndoLocal(state);
729
- const nextLineContent = currentLine(cursorRow + 1);
730
- const newLines = [...nextState.lines];
731
- newLines[cursorRow] = lineContent + nextLineContent;
732
- newLines.splice(cursorRow + 1, 1);
733
- return {
734
- ...nextState,
735
- lines: newLines,
736
- preferredCol: null,
737
- };
738
- }
739
- return state;
740
- }
741
- case "kill_line_left": {
742
- const { cursorRow, cursorCol } = state;
743
- if (cursorCol > 0) {
744
- const nextState = pushUndoLocal(state);
745
- const lineContent = currentLine(cursorRow);
746
- const newLines = [...nextState.lines];
747
- newLines[cursorRow] = cpSlice(lineContent, cursorCol);
748
- return {
749
- ...nextState,
750
- lines: newLines,
751
- cursorCol: 0,
752
- preferredCol: null,
753
- };
754
- }
755
- return state;
756
- }
757
- case "undo": {
758
- const stateToRestore = state.undoStack[state.undoStack.length - 1];
759
- if (!stateToRestore)
760
- return state;
761
- const currentSnapshot = {
762
- lines: [...state.lines],
763
- cursorRow: state.cursorRow,
764
- cursorCol: state.cursorCol,
765
- };
766
- return {
767
- ...state,
768
- ...stateToRestore,
769
- undoStack: state.undoStack.slice(0, -1),
770
- redoStack: [...state.redoStack, currentSnapshot],
771
- };
772
- }
773
- case "redo": {
774
- const stateToRestore = state.redoStack[state.redoStack.length - 1];
775
- if (!stateToRestore)
776
- return state;
777
- const currentSnapshot = {
778
- lines: [...state.lines],
779
- cursorRow: state.cursorRow,
780
- cursorCol: state.cursorCol,
781
- };
782
- return {
783
- ...state,
784
- ...stateToRestore,
785
- redoStack: state.redoStack.slice(0, -1),
786
- undoStack: [...state.undoStack, currentSnapshot],
787
- };
788
- }
789
- case "replace_range": {
790
- const { startRow, startCol, endRow, endCol, text } = action.payload;
791
- const nextState = pushUndoLocal(state);
792
- return replaceRangeInternal(nextState, startRow, startCol, endRow, endCol, text);
793
- }
794
- case "move_to_offset": {
795
- const { offset } = action.payload;
796
- const [newRow, newCol] = offsetToLogicalPos(state.lines.join("\n"), offset);
797
- return {
798
- ...state,
799
- cursorRow: newRow,
800
- cursorCol: newCol,
801
- preferredCol: null,
802
- };
803
- }
804
- case "create_undo_snapshot": {
805
- return pushUndoLocal(state);
806
- }
807
- default: {
808
- const exhaustiveCheck = action;
809
- console.error(`Unknown action encountered: ${exhaustiveCheck}`);
810
- return state;
811
- }
812
- }
813
- }
814
- function textBufferReducer(state, action) {
815
- const newState = textBufferReducerLogic(state, action);
816
- if (newState.lines !== state.lines || newState.viewportWidth !== state.viewportWidth) {
817
- return {
818
- ...newState,
819
- visualLayout: calculateLayout(newState.lines, newState.viewportWidth),
820
- };
821
- }
822
- return newState;
823
- }
824
- // --- End of reducer logic ---
825
- export function useTextBuffer({ initialText = "", initialCursorOffset = 0, viewport, onChange, isValidPath, shellModeActive = false, }) {
826
- const initialState = useMemo(() => {
827
- const lines = initialText.split("\n");
828
- const [initialCursorRow, initialCursorCol] = calculateInitialCursorPosition(lines.length === 0 ? [""] : lines, initialCursorOffset);
829
- const visualLayout = calculateLayout(lines.length === 0 ? [""] : lines, viewport.width);
830
- return {
831
- lines: lines.length === 0 ? [""] : lines,
832
- cursorRow: initialCursorRow,
833
- cursorCol: initialCursorCol,
834
- preferredCol: null,
835
- undoStack: [],
836
- redoStack: [],
837
- clipboard: null,
838
- selectionAnchor: null,
839
- viewportWidth: viewport.width,
840
- viewportHeight: viewport.height,
841
- visualLayout,
842
- };
843
- }, [initialText, initialCursorOffset, viewport.width, viewport.height]);
844
- const [state, dispatch] = useReducer(textBufferReducer, initialState);
845
- const { lines, cursorRow, cursorCol, preferredCol, selectionAnchor, visualLayout } = state;
846
- const text = useMemo(() => lines.join("\n"), [lines]);
847
- const visualCursor = useMemo(() => calculateVisualCursorFromLayout(visualLayout, [cursorRow, cursorCol]), [visualLayout, cursorRow, cursorCol]);
848
- const { visualLines, visualToLogicalMap } = visualLayout;
849
- const [visualScrollRow, setVisualScrollRow] = useState(0);
850
- useEffect(() => {
851
- if (onChange) {
852
- onChange(text);
853
- }
854
- }, [text, onChange]);
855
- useEffect(() => {
856
- dispatch({
857
- type: "set_viewport",
858
- payload: { width: viewport.width, height: viewport.height },
859
- });
860
- }, [viewport.width, viewport.height]);
861
- // Update visual scroll (vertical)
862
- useEffect(() => {
863
- const { height } = viewport;
864
- const totalVisualLines = visualLines.length;
865
- const maxScrollStart = Math.max(0, totalVisualLines - height);
866
- let newVisualScrollRow = visualScrollRow;
867
- if (visualCursor[0] < visualScrollRow) {
868
- newVisualScrollRow = visualCursor[0];
869
- }
870
- else if (visualCursor[0] >= visualScrollRow + height) {
871
- newVisualScrollRow = visualCursor[0] - height + 1;
872
- }
873
- // When the number of visual lines shrinks (e.g., after widening the viewport),
874
- // ensure scroll never starts beyond the last valid start so we can render a full window.
875
- newVisualScrollRow = clamp(newVisualScrollRow, 0, maxScrollStart);
876
- if (newVisualScrollRow !== visualScrollRow) {
877
- setVisualScrollRow(newVisualScrollRow);
878
- }
879
- }, [visualCursor, visualScrollRow, viewport, visualLines.length]);
880
- const insert = useCallback((ch, { paste = false } = {}) => {
881
- if (/[\n\r]/.test(ch)) {
882
- dispatch({ type: "insert", payload: ch });
883
- return;
884
- }
885
- const minLengthToInferAsDragDrop = 3;
886
- if (ch.length >= minLengthToInferAsDragDrop && !shellModeActive && paste) {
887
- let potentialPath = ch.trim();
888
- const quoteMatch = potentialPath.match(/^'(.*)'$/);
889
- if (quoteMatch) {
890
- potentialPath = quoteMatch[1];
891
- }
892
- potentialPath = potentialPath.trim();
893
- if (isValidPath(potentialPath)) {
894
- ch = `@${potentialPath} `;
895
- }
896
- }
897
- let currentText = "";
898
- for (const char of toCodePoints(ch)) {
899
- if (char.codePointAt(0) === 127) {
900
- if (currentText.length > 0) {
901
- dispatch({ type: "insert", payload: currentText });
902
- currentText = "";
903
- }
904
- dispatch({ type: "backspace" });
905
- }
906
- else {
907
- currentText += char;
908
- }
909
- }
910
- if (currentText.length > 0) {
911
- dispatch({ type: "insert", payload: currentText });
912
- }
913
- }, [isValidPath, shellModeActive]);
914
- const newline = useCallback(() => {
915
- dispatch({ type: "insert", payload: "\n" });
916
- }, []);
917
- const backspace = useCallback(() => {
918
- dispatch({ type: "backspace" });
919
- }, []);
920
- const del = useCallback(() => {
921
- dispatch({ type: "delete" });
922
- }, []);
923
- const move = useCallback((dir) => {
924
- dispatch({ type: "move", payload: { dir } });
925
- }, [dispatch]);
926
- const undo = useCallback(() => {
927
- dispatch({ type: "undo" });
928
- }, []);
929
- const redo = useCallback(() => {
930
- dispatch({ type: "redo" });
931
- }, []);
932
- const setText = useCallback((newText) => {
933
- dispatch({ type: "set_text", payload: newText });
934
- }, []);
935
- const deleteWordLeft = useCallback(() => {
936
- dispatch({ type: "delete_word_left" });
937
- }, []);
938
- const deleteWordRight = useCallback(() => {
939
- dispatch({ type: "delete_word_right" });
940
- }, []);
941
- const killLineRight = useCallback(() => {
942
- dispatch({ type: "kill_line_right" });
943
- }, []);
944
- const killLineLeft = useCallback(() => {
945
- dispatch({ type: "kill_line_left" });
946
- }, []);
947
- const handleInput = useCallback((key) => {
948
- const { sequence: input } = key;
949
- if (key.paste) {
950
- // Do not do any other processing on pastes so ensure we handle them
951
- // before all other cases.
952
- insert(input, { paste: key.paste });
953
- return;
954
- }
955
- if (key.name === "return" ||
956
- input === "\r" ||
957
- input === "\n" ||
958
- input === "\\\r" // VSCode terminal represents shift + enter this way
959
- )
960
- newline();
961
- else if (key.name === "left" && !key.meta && !key.ctrl)
962
- move("left");
963
- else if (key.ctrl && key.name === "b")
964
- move("left");
965
- else if (key.name === "right" && !key.meta && !key.ctrl)
966
- move("right");
967
- else if (key.ctrl && key.name === "f")
968
- move("right");
969
- else if (key.name === "up")
970
- move("up");
971
- else if (key.name === "down")
972
- move("down");
973
- else if ((key.ctrl || key.meta) && key.name === "left")
974
- move("wordLeft");
975
- else if (key.meta && key.name === "b")
976
- move("wordLeft");
977
- else if ((key.ctrl || key.meta) && key.name === "right")
978
- move("wordRight");
979
- else if (key.meta && key.name === "f")
980
- move("wordRight");
981
- else if (key.name === "home")
982
- move("home");
983
- else if (key.ctrl && key.name === "a")
984
- move("home");
985
- else if (key.name === "end")
986
- move("end");
987
- else if (key.ctrl && key.name === "e")
988
- move("end");
989
- else if (key.ctrl && key.name === "w")
990
- deleteWordLeft();
991
- else if ((key.meta || key.ctrl) && (key.name === "backspace" || input === "\x7f"))
992
- deleteWordLeft();
993
- else if ((key.meta || key.ctrl) && key.name === "delete")
994
- deleteWordRight();
995
- else if (key.name === "backspace" || input === "\x7f" || (key.ctrl && key.name === "h"))
996
- backspace();
997
- else if (key.name === "delete" || (key.ctrl && key.name === "d"))
998
- del();
999
- else if (key.ctrl && !key.shift && key.name === "z")
1000
- undo();
1001
- else if (key.ctrl && key.shift && key.name === "z")
1002
- redo();
1003
- else if (input && !key.ctrl && !key.meta) {
1004
- insert(input, { paste: key.paste });
1005
- }
1006
- }, [newline, move, deleteWordLeft, deleteWordRight, backspace, del, insert, undo, redo]);
1007
- const renderedVisualLines = useMemo(() => visualLines.slice(visualScrollRow, visualScrollRow + viewport.height), [visualLines, visualScrollRow, viewport.height]);
1008
- const returnValue = useMemo(() => ({
1009
- lines,
1010
- text,
1011
- cursor: [cursorRow, cursorCol],
1012
- preferredCol,
1013
- selectionAnchor,
1014
- allVisualLines: visualLines,
1015
- viewportVisualLines: renderedVisualLines,
1016
- visualCursor,
1017
- visualScrollRow,
1018
- visualToLogicalMap,
1019
- setText,
1020
- insert,
1021
- newline,
1022
- backspace,
1023
- del,
1024
- move,
1025
- undo,
1026
- redo,
1027
- deleteWordLeft,
1028
- deleteWordRight,
1029
- killLineRight,
1030
- killLineLeft,
1031
- handleInput,
1032
- }), [
1033
- lines,
1034
- text,
1035
- cursorRow,
1036
- cursorCol,
1037
- preferredCol,
1038
- selectionAnchor,
1039
- visualLines,
1040
- renderedVisualLines,
1041
- visualCursor,
1042
- visualScrollRow,
1043
- setText,
1044
- insert,
1045
- newline,
1046
- backspace,
1047
- del,
1048
- move,
1049
- undo,
1050
- redo,
1051
- deleteWordLeft,
1052
- deleteWordRight,
1053
- killLineRight,
1054
- killLineLeft,
1055
- handleInput,
1056
- visualToLogicalMap,
1057
- ]);
1058
- return returnValue;
1059
- }