@fairyhunter13/opentui-core 0.1.112 → 0.1.114

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 (591) hide show
  1. package/dev/keypress-debug-renderer.ts +148 -0
  2. package/dev/keypress-debug.ts +43 -0
  3. package/dev/print-env-vars.ts +32 -0
  4. package/dev/test-tmux-graphics-334.sh +68 -0
  5. package/dev/thai-debug-test.ts +68 -0
  6. package/docs/development.md +144 -0
  7. package/package.json +63 -51
  8. package/scripts/build.ts +400 -0
  9. package/scripts/publish.ts +60 -0
  10. package/src/3d/SpriteResourceManager.ts +286 -0
  11. package/src/3d/SpriteUtils.ts +70 -0
  12. package/src/3d/TextureUtils.ts +196 -0
  13. package/src/3d/ThreeRenderable.ts +197 -0
  14. package/src/3d/WGPURenderer.ts +294 -0
  15. package/src/3d/animation/ExplodingSpriteEffect.ts +513 -0
  16. package/src/3d/animation/PhysicsExplodingSpriteEffect.ts +429 -0
  17. package/src/3d/animation/SpriteAnimator.ts +633 -0
  18. package/src/3d/animation/SpriteParticleGenerator.ts +435 -0
  19. package/src/3d/canvas.ts +464 -0
  20. package/src/3d/index.ts +12 -0
  21. package/src/3d/physics/PlanckPhysicsAdapter.ts +72 -0
  22. package/src/3d/physics/RapierPhysicsAdapter.ts +66 -0
  23. package/src/3d/physics/physics-interface.ts +31 -0
  24. package/src/3d/shaders/supersampling.wgsl +201 -0
  25. package/src/3d.ts +3 -0
  26. package/src/NativeSpanFeed.ts +300 -0
  27. package/src/Renderable.ts +1704 -0
  28. package/src/__snapshots__/buffer.test.ts.snap +28 -0
  29. package/src/animation/Timeline.test.ts +2709 -0
  30. package/src/animation/Timeline.ts +598 -0
  31. package/src/ansi.ts +18 -0
  32. package/src/benchmark/attenuation-benchmark.ts +81 -0
  33. package/src/benchmark/colormatrix-benchmark.ts +128 -0
  34. package/src/benchmark/gain-benchmark.ts +80 -0
  35. package/src/benchmark/latest-all-bench-run.json +707 -0
  36. package/src/benchmark/latest-async-bench-run.json +336 -0
  37. package/src/benchmark/latest-default-bench-run.json +657 -0
  38. package/src/benchmark/latest-large-bench-run.json +707 -0
  39. package/src/benchmark/latest-quick-bench-run.json +207 -0
  40. package/src/benchmark/markdown-benchmark.ts +1796 -0
  41. package/src/benchmark/native-span-feed-async-benchmark.ts +355 -0
  42. package/src/benchmark/native-span-feed-benchmark.md +56 -0
  43. package/src/benchmark/native-span-feed-benchmark.ts +596 -0
  44. package/src/benchmark/native-span-feed-compare.ts +280 -0
  45. package/src/benchmark/renderer-benchmark.ts +754 -0
  46. package/src/benchmark/text-table-benchmark.ts +948 -0
  47. package/src/buffer.test.ts +291 -0
  48. package/src/buffer.ts +554 -0
  49. package/src/console.test.ts +612 -0
  50. package/src/console.ts +1254 -0
  51. package/src/edit-buffer.test.ts +1769 -0
  52. package/src/edit-buffer.ts +411 -0
  53. package/src/editor-view.test.ts +1032 -0
  54. package/src/editor-view.ts +284 -0
  55. package/src/examples/ascii-font-selection-demo.ts +245 -0
  56. package/src/examples/assets/Water_2_M_Normal.jpg +0 -0
  57. package/src/examples/assets/concrete.png +0 -0
  58. package/src/examples/assets/crate.png +0 -0
  59. package/src/examples/assets/crate_emissive.png +0 -0
  60. package/src/examples/assets/forrest_background.png +0 -0
  61. package/src/examples/assets/hast-example.json +1018 -0
  62. package/src/examples/assets/heart.png +0 -0
  63. package/src/examples/assets/main_char_heavy_attack.png +0 -0
  64. package/src/examples/assets/main_char_idle.png +0 -0
  65. package/src/examples/assets/main_char_jump_end.png +0 -0
  66. package/src/examples/assets/main_char_jump_landing.png +0 -0
  67. package/src/examples/assets/main_char_jump_start.png +0 -0
  68. package/src/examples/assets/main_char_run_loop.png +0 -0
  69. package/src/examples/assets/roughness_map.jpg +0 -0
  70. package/src/examples/build.ts +115 -0
  71. package/src/examples/code-demo.ts +924 -0
  72. package/src/examples/console-demo.ts +358 -0
  73. package/src/examples/core-plugin-slots-demo.ts +759 -0
  74. package/src/examples/diff-demo.ts +701 -0
  75. package/src/examples/draggable-three-demo.ts +259 -0
  76. package/src/examples/editor-demo.ts +322 -0
  77. package/src/examples/extmarks-demo.ts +196 -0
  78. package/src/examples/focus-restore-demo.ts +310 -0
  79. package/src/examples/fonts.ts +245 -0
  80. package/src/examples/fractal-shader-demo.ts +268 -0
  81. package/src/examples/framebuffer-demo.ts +674 -0
  82. package/src/examples/full-unicode-demo.ts +241 -0
  83. package/src/examples/golden-star-demo.ts +933 -0
  84. package/src/examples/grayscale-buffer-demo.ts +249 -0
  85. package/src/examples/hast-syntax-highlighting-demo.ts +129 -0
  86. package/src/examples/index.ts +926 -0
  87. package/src/examples/input-demo.ts +377 -0
  88. package/src/examples/input-select-layout-demo.ts +425 -0
  89. package/src/examples/install.sh +143 -0
  90. package/src/examples/keypress-debug-demo.ts +452 -0
  91. package/src/examples/lib/HexList.ts +122 -0
  92. package/src/examples/lib/PaletteGrid.ts +125 -0
  93. package/src/examples/lib/standalone-keys.ts +25 -0
  94. package/src/examples/lib/tab-controller.ts +243 -0
  95. package/src/examples/lights-phong-demo.ts +290 -0
  96. package/src/examples/link-demo.ts +220 -0
  97. package/src/examples/live-state-demo.ts +480 -0
  98. package/src/examples/markdown-demo.ts +725 -0
  99. package/src/examples/mouse-interaction-demo.ts +428 -0
  100. package/src/examples/nested-zindex-demo.ts +357 -0
  101. package/src/examples/opacity-example.ts +235 -0
  102. package/src/examples/opentui-demo.ts +1057 -0
  103. package/src/examples/physx-planck-2d-demo.ts +623 -0
  104. package/src/examples/physx-rapier-2d-demo.ts +655 -0
  105. package/src/examples/relative-positioning-demo.ts +323 -0
  106. package/src/examples/scroll-example.ts +214 -0
  107. package/src/examples/scrollbox-mouse-test.ts +112 -0
  108. package/src/examples/scrollbox-overlay-hit-test.ts +206 -0
  109. package/src/examples/select-demo.ts +237 -0
  110. package/src/examples/shader-cube-demo.ts +1015 -0
  111. package/src/examples/simple-layout-example.ts +591 -0
  112. package/src/examples/slider-demo.ts +617 -0
  113. package/src/examples/split-mode-demo.ts +453 -0
  114. package/src/examples/sprite-animation-demo.ts +443 -0
  115. package/src/examples/sprite-particle-generator-demo.ts +486 -0
  116. package/src/examples/static-sprite-demo.ts +193 -0
  117. package/src/examples/sticky-scroll-example.ts +308 -0
  118. package/src/examples/styled-text-demo.ts +282 -0
  119. package/src/examples/tab-select-demo.ts +219 -0
  120. package/src/examples/terminal-title.ts +29 -0
  121. package/src/examples/terminal.ts +305 -0
  122. package/src/examples/text-node-demo.ts +416 -0
  123. package/src/examples/text-selection-demo.ts +377 -0
  124. package/src/examples/text-table-demo.ts +503 -0
  125. package/src/examples/text-truncation-demo.ts +481 -0
  126. package/src/examples/text-wrap.ts +757 -0
  127. package/src/examples/texture-loading-demo.ts +259 -0
  128. package/src/examples/timeline-example.ts +670 -0
  129. package/src/examples/transparency-demo.ts +400 -0
  130. package/src/examples/vnode-composition-demo.ts +404 -0
  131. package/src/examples/wide-grapheme-overlay-demo.ts +280 -0
  132. package/src/index.ts +24 -0
  133. package/src/lib/KeyHandler.integration.test.ts +292 -0
  134. package/src/lib/KeyHandler.stopPropagation.test.ts +289 -0
  135. package/src/lib/KeyHandler.test.ts +662 -0
  136. package/src/lib/KeyHandler.ts +222 -0
  137. package/src/lib/RGBA.test.ts +984 -0
  138. package/src/lib/RGBA.ts +204 -0
  139. package/src/lib/ascii.font.ts +330 -0
  140. package/src/lib/border.test.ts +83 -0
  141. package/src/lib/border.ts +170 -0
  142. package/src/lib/bunfs.test.ts +27 -0
  143. package/src/lib/bunfs.ts +18 -0
  144. package/src/lib/clipboard.test.ts +41 -0
  145. package/src/lib/clipboard.ts +47 -0
  146. package/src/lib/clock.ts +35 -0
  147. package/src/lib/data-paths.test.ts +133 -0
  148. package/src/lib/data-paths.ts +109 -0
  149. package/src/lib/debounce.ts +106 -0
  150. package/src/lib/detect-links.test.ts +98 -0
  151. package/src/lib/detect-links.ts +56 -0
  152. package/src/lib/env.test.ts +228 -0
  153. package/src/lib/env.ts +209 -0
  154. package/src/lib/extmarks-history.ts +51 -0
  155. package/src/lib/extmarks-multiwidth.test.ts +322 -0
  156. package/src/lib/extmarks.test.ts +3457 -0
  157. package/src/lib/extmarks.ts +843 -0
  158. package/src/lib/fonts/block.json +405 -0
  159. package/src/lib/fonts/grid.json +265 -0
  160. package/src/lib/fonts/huge.json +741 -0
  161. package/src/lib/fonts/pallet.json +314 -0
  162. package/src/lib/fonts/shade.json +591 -0
  163. package/src/lib/fonts/slick.json +321 -0
  164. package/src/lib/fonts/tiny.json +69 -0
  165. package/src/lib/hast-styled-text.ts +59 -0
  166. package/src/lib/index.ts +21 -0
  167. package/src/lib/keymapping.test.ts +317 -0
  168. package/src/lib/keymapping.ts +115 -0
  169. package/src/lib/objects-in-viewport.test.ts +787 -0
  170. package/src/lib/objects-in-viewport.ts +153 -0
  171. package/src/lib/output.capture.ts +58 -0
  172. package/src/lib/parse.keypress-kitty.protocol.test.ts +340 -0
  173. package/src/lib/parse.keypress-kitty.test.ts +663 -0
  174. package/src/lib/parse.keypress-kitty.ts +439 -0
  175. package/src/lib/parse.keypress.test.ts +1849 -0
  176. package/src/lib/parse.keypress.ts +397 -0
  177. package/src/lib/parse.mouse.test.ts +552 -0
  178. package/src/lib/parse.mouse.ts +232 -0
  179. package/src/lib/paste.ts +16 -0
  180. package/src/lib/queue.ts +65 -0
  181. package/src/lib/renderable.validations.test.ts +87 -0
  182. package/src/lib/renderable.validations.ts +83 -0
  183. package/src/lib/scroll-acceleration.ts +98 -0
  184. package/src/lib/selection.ts +240 -0
  185. package/src/lib/singleton.ts +28 -0
  186. package/src/lib/stdin-parser.test.ts +2290 -0
  187. package/src/lib/stdin-parser.ts +1810 -0
  188. package/src/lib/styled-text.ts +178 -0
  189. package/src/lib/terminal-capability-detection.test.ts +202 -0
  190. package/src/lib/terminal-capability-detection.ts +79 -0
  191. package/src/lib/terminal-palette.test.ts +878 -0
  192. package/src/lib/terminal-palette.ts +383 -0
  193. package/src/lib/tree-sitter/assets/README.md +118 -0
  194. package/src/lib/tree-sitter/assets/update.ts +334 -0
  195. package/src/lib/tree-sitter/assets.d.ts +9 -0
  196. package/src/lib/tree-sitter/cache.test.ts +273 -0
  197. package/src/lib/tree-sitter/client.test.ts +1165 -0
  198. package/src/lib/tree-sitter/client.ts +607 -0
  199. package/src/lib/tree-sitter/default-parsers.ts +86 -0
  200. package/src/lib/tree-sitter/download-utils.ts +148 -0
  201. package/src/lib/tree-sitter/index.ts +28 -0
  202. package/src/lib/tree-sitter/parser.worker.ts +1042 -0
  203. package/src/lib/tree-sitter/parsers-config.ts +81 -0
  204. package/src/lib/tree-sitter/resolve-ft.test.ts +55 -0
  205. package/src/lib/tree-sitter/resolve-ft.ts +189 -0
  206. package/src/lib/tree-sitter/types.ts +82 -0
  207. package/src/lib/tree-sitter-styled-text.test.ts +1253 -0
  208. package/src/lib/tree-sitter-styled-text.ts +306 -0
  209. package/src/lib/validate-dir-name.ts +55 -0
  210. package/src/lib/yoga.options.test.ts +628 -0
  211. package/src/lib/yoga.options.ts +346 -0
  212. package/src/plugins/core-slot.ts +579 -0
  213. package/src/plugins/registry.ts +402 -0
  214. package/src/plugins/types.ts +46 -0
  215. package/src/post/effects.ts +930 -0
  216. package/src/post/filters.ts +489 -0
  217. package/src/post/matrices.ts +288 -0
  218. package/src/renderables/ASCIIFont.ts +219 -0
  219. package/src/renderables/Box.test.ts +205 -0
  220. package/src/renderables/Box.ts +326 -0
  221. package/src/renderables/Code.test.ts +2062 -0
  222. package/src/renderables/Code.ts +357 -0
  223. package/src/renderables/Diff.regression.test.ts +226 -0
  224. package/src/renderables/Diff.test.ts +3101 -0
  225. package/src/renderables/Diff.ts +1211 -0
  226. package/src/renderables/EditBufferRenderable.test.ts +288 -0
  227. package/src/renderables/EditBufferRenderable.ts +1166 -0
  228. package/src/renderables/FrameBuffer.ts +47 -0
  229. package/src/renderables/Input.test.ts +1228 -0
  230. package/src/renderables/Input.ts +247 -0
  231. package/src/renderables/LineNumberRenderable.ts +724 -0
  232. package/src/renderables/Markdown.ts +1393 -0
  233. package/src/renderables/ScrollBar.ts +422 -0
  234. package/src/renderables/ScrollBox.ts +883 -0
  235. package/src/renderables/Select.test.ts +1033 -0
  236. package/src/renderables/Select.ts +524 -0
  237. package/src/renderables/Slider.test.ts +456 -0
  238. package/src/renderables/Slider.ts +342 -0
  239. package/src/renderables/TabSelect.test.ts +197 -0
  240. package/src/renderables/TabSelect.ts +455 -0
  241. package/src/renderables/Text.selection-buffer.test.ts +123 -0
  242. package/src/renderables/Text.test.ts +2660 -0
  243. package/src/renderables/Text.ts +147 -0
  244. package/src/renderables/TextBufferRenderable.ts +518 -0
  245. package/src/renderables/TextNode.test.ts +1058 -0
  246. package/src/renderables/TextNode.ts +325 -0
  247. package/src/renderables/TextTable.test.ts +1421 -0
  248. package/src/renderables/TextTable.ts +1344 -0
  249. package/src/renderables/Textarea.ts +430 -0
  250. package/src/renderables/TimeToFirstDraw.ts +89 -0
  251. package/src/renderables/__snapshots__/Code.test.ts.snap +13 -0
  252. package/src/renderables/__snapshots__/Diff.test.ts.snap +785 -0
  253. package/src/renderables/__snapshots__/Text.test.ts.snap +421 -0
  254. package/src/renderables/__snapshots__/TextTable.test.ts.snap +215 -0
  255. package/src/renderables/__tests__/LineNumberRenderable.scrollbox-simple.test.ts +144 -0
  256. package/src/renderables/__tests__/LineNumberRenderable.scrollbox.test.ts +816 -0
  257. package/src/renderables/__tests__/LineNumberRenderable.test.ts +1865 -0
  258. package/src/renderables/__tests__/LineNumberRenderable.wrapping.test.ts +85 -0
  259. package/src/renderables/__tests__/Markdown.code-colors.test.ts +242 -0
  260. package/src/renderables/__tests__/Markdown.test.ts +2518 -0
  261. package/src/renderables/__tests__/MultiRenderable.selection.test.ts +87 -0
  262. package/src/renderables/__tests__/Textarea.buffer.test.ts +682 -0
  263. package/src/renderables/__tests__/Textarea.destroyed-events.test.ts +675 -0
  264. package/src/renderables/__tests__/Textarea.editing.test.ts +2041 -0
  265. package/src/renderables/__tests__/Textarea.error-handling.test.ts +35 -0
  266. package/src/renderables/__tests__/Textarea.events.test.ts +738 -0
  267. package/src/renderables/__tests__/Textarea.highlights.test.ts +590 -0
  268. package/src/renderables/__tests__/Textarea.keybinding.test.ts +3149 -0
  269. package/src/renderables/__tests__/Textarea.paste.test.ts +357 -0
  270. package/src/renderables/__tests__/Textarea.rendering.test.ts +1866 -0
  271. package/src/renderables/__tests__/Textarea.scroll.test.ts +733 -0
  272. package/src/renderables/__tests__/Textarea.selection.test.ts +1590 -0
  273. package/src/renderables/__tests__/Textarea.stress.test.ts +670 -0
  274. package/src/renderables/__tests__/Textarea.undo-redo.test.ts +383 -0
  275. package/src/renderables/__tests__/Textarea.visual-lines.test.ts +310 -0
  276. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.code.test.ts.snap +221 -0
  277. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.scrollbox-simple.test.ts.snap +89 -0
  278. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.scrollbox.test.ts.snap +457 -0
  279. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.test.ts.snap +158 -0
  280. package/src/renderables/__tests__/__snapshots__/Textarea.rendering.test.ts.snap +387 -0
  281. package/src/renderables/__tests__/markdown-parser.test.ts +217 -0
  282. package/src/renderables/__tests__/renderable-test-utils.ts +60 -0
  283. package/src/renderables/composition/README.md +8 -0
  284. package/src/renderables/composition/VRenderable.ts +32 -0
  285. package/src/renderables/composition/constructs.ts +127 -0
  286. package/src/renderables/composition/vnode.ts +289 -0
  287. package/src/renderables/index.ts +23 -0
  288. package/src/renderables/markdown-parser.ts +66 -0
  289. package/src/renderer.ts +2681 -0
  290. package/src/runtime-plugin-support.ts +39 -0
  291. package/src/runtime-plugin.ts +615 -0
  292. package/src/syntax-style.test.ts +841 -0
  293. package/src/syntax-style.ts +257 -0
  294. package/src/testing/README.md +210 -0
  295. package/src/testing/capture-spans.test.ts +194 -0
  296. package/src/testing/integration.test.ts +276 -0
  297. package/src/testing/manual-clock.ts +117 -0
  298. package/src/testing/mock-keys.test.ts +1378 -0
  299. package/src/testing/mock-keys.ts +457 -0
  300. package/src/testing/mock-mouse.test.ts +218 -0
  301. package/src/testing/mock-mouse.ts +247 -0
  302. package/src/testing/mock-tree-sitter-client.ts +73 -0
  303. package/src/testing/spy.ts +13 -0
  304. package/src/testing/test-recorder.test.ts +415 -0
  305. package/src/testing/test-recorder.ts +145 -0
  306. package/src/testing/test-renderer.ts +132 -0
  307. package/src/testing.ts +7 -0
  308. package/src/tests/__snapshots__/absolute-positioning.snapshot.test.ts.snap +481 -0
  309. package/src/tests/__snapshots__/renderable.snapshot.test.ts.snap +19 -0
  310. package/src/tests/__snapshots__/scrollbox.test.ts.snap +29 -0
  311. package/src/tests/absolute-positioning.snapshot.test.ts +638 -0
  312. package/src/tests/allocator-stats.test.ts +38 -0
  313. package/src/tests/destroy-during-render.test.ts +200 -0
  314. package/src/tests/destroy-on-exit.fixture.ts +36 -0
  315. package/src/tests/destroy-on-exit.test.ts +41 -0
  316. package/src/tests/hover-cursor.test.ts +98 -0
  317. package/src/tests/native-span-feed-async.test.ts +173 -0
  318. package/src/tests/native-span-feed-close.test.ts +120 -0
  319. package/src/tests/native-span-feed-coverage.test.ts +227 -0
  320. package/src/tests/native-span-feed-edge-cases.test.ts +352 -0
  321. package/src/tests/native-span-feed-use-after-free.test.ts +45 -0
  322. package/src/tests/opacity.test.ts +123 -0
  323. package/src/tests/renderable.snapshot.test.ts +524 -0
  324. package/src/tests/renderable.test.ts +1281 -0
  325. package/src/tests/renderer.clock.test.ts +158 -0
  326. package/src/tests/renderer.console-startup.test.ts +185 -0
  327. package/src/tests/renderer.control.test.ts +425 -0
  328. package/src/tests/renderer.core-slot-binding.test.ts +952 -0
  329. package/src/tests/renderer.cursor.test.ts +26 -0
  330. package/src/tests/renderer.destroy-during-render.test.ts +147 -0
  331. package/src/tests/renderer.focus-restore.test.ts +257 -0
  332. package/src/tests/renderer.focus.test.ts +294 -0
  333. package/src/tests/renderer.idle.test.ts +219 -0
  334. package/src/tests/renderer.input.test.ts +2237 -0
  335. package/src/tests/renderer.kitty-flags.test.ts +195 -0
  336. package/src/tests/renderer.mouse.test.ts +1274 -0
  337. package/src/tests/renderer.palette.test.ts +629 -0
  338. package/src/tests/renderer.selection.test.ts +49 -0
  339. package/src/tests/renderer.slot-registry.test.ts +684 -0
  340. package/src/tests/renderer.useMouse.test.ts +47 -0
  341. package/src/tests/runtime-plugin-node-modules-cycle.fixture.ts +76 -0
  342. package/src/tests/runtime-plugin-node-modules-mjs.fixture.ts +43 -0
  343. package/src/tests/runtime-plugin-node-modules-no-bare-rewrite.fixture.ts +67 -0
  344. package/src/tests/runtime-plugin-node-modules-package-type-cache.fixture.ts +72 -0
  345. package/src/tests/runtime-plugin-node-modules-runtime-specifier.fixture.ts +44 -0
  346. package/src/tests/runtime-plugin-node-modules-scoped-package-bare-rewrite.fixture.ts +85 -0
  347. package/src/tests/runtime-plugin-path-alias.fixture.ts +43 -0
  348. package/src/tests/runtime-plugin-resolve-roots.fixture.ts +65 -0
  349. package/src/tests/runtime-plugin-support.fixture.ts +11 -0
  350. package/src/tests/runtime-plugin-support.test.ts +19 -0
  351. package/src/tests/runtime-plugin-windows-file-url.fixture.ts +30 -0
  352. package/src/tests/runtime-plugin.fixture.ts +40 -0
  353. package/src/tests/runtime-plugin.test.ts +354 -0
  354. package/src/tests/scrollbox-culling-bug.test.ts +114 -0
  355. package/src/tests/scrollbox-hitgrid-resize.test.ts +136 -0
  356. package/src/tests/scrollbox-hitgrid.test.ts +909 -0
  357. package/src/tests/scrollbox.test.ts +1530 -0
  358. package/src/tests/wrap-resize-perf.test.ts +276 -0
  359. package/src/tests/yoga-setters.test.ts +921 -0
  360. package/src/text-buffer-view.test.ts +705 -0
  361. package/src/text-buffer-view.ts +189 -0
  362. package/src/text-buffer.test.ts +347 -0
  363. package/src/text-buffer.ts +250 -0
  364. package/src/types.ts +161 -0
  365. package/src/utils.ts +88 -0
  366. package/src/zig/ansi.zig +268 -0
  367. package/src/zig/bench/README.md +50 -0
  368. package/src/zig/bench/buffer-draw-text-buffer_bench.zig +887 -0
  369. package/src/zig/bench/edit-buffer_bench.zig +476 -0
  370. package/src/zig/bench/native-span-feed_bench.zig +100 -0
  371. package/src/zig/bench/rope-markers_bench.zig +713 -0
  372. package/src/zig/bench/rope_bench.zig +514 -0
  373. package/src/zig/bench/styled-text_bench.zig +470 -0
  374. package/src/zig/bench/text-buffer-coords_bench.zig +362 -0
  375. package/src/zig/bench/text-buffer-view_bench.zig +459 -0
  376. package/src/zig/bench/text-chunk-graphemes_bench.zig +273 -0
  377. package/src/zig/bench/utf8_bench.zig +799 -0
  378. package/src/zig/bench-utils.zig +431 -0
  379. package/src/zig/bench.zig +217 -0
  380. package/src/zig/buffer-methods.zig +211 -0
  381. package/src/zig/buffer.zig +2281 -0
  382. package/src/zig/build.zig +289 -0
  383. package/src/zig/build.zig.zon +16 -0
  384. package/src/zig/edit-buffer.zig +825 -0
  385. package/src/zig/editor-view.zig +802 -0
  386. package/src/zig/event-bus.zig +13 -0
  387. package/src/zig/event-emitter.zig +65 -0
  388. package/src/zig/file-logger.zig +92 -0
  389. package/src/zig/grapheme.zig +599 -0
  390. package/src/zig/lib.zig +1854 -0
  391. package/src/zig/link.zig +333 -0
  392. package/src/zig/logger.zig +43 -0
  393. package/src/zig/mem-registry.zig +125 -0
  394. package/src/zig/native-span-feed-bench-lib.zig +7 -0
  395. package/src/zig/native-span-feed.zig +708 -0
  396. package/src/zig/renderer.zig +1393 -0
  397. package/src/zig/rope.zig +1220 -0
  398. package/src/zig/syntax-style.zig +161 -0
  399. package/src/zig/terminal.zig +987 -0
  400. package/src/zig/test.zig +72 -0
  401. package/src/zig/tests/README.md +18 -0
  402. package/src/zig/tests/buffer-methods_test.zig +1109 -0
  403. package/src/zig/tests/buffer_test.zig +2557 -0
  404. package/src/zig/tests/edit-buffer-history_test.zig +271 -0
  405. package/src/zig/tests/edit-buffer_test.zig +1689 -0
  406. package/src/zig/tests/editor-view_test.zig +3299 -0
  407. package/src/zig/tests/event-emitter_test.zig +249 -0
  408. package/src/zig/tests/grapheme_test.zig +1304 -0
  409. package/src/zig/tests/link_test.zig +190 -0
  410. package/src/zig/tests/mem-registry_test.zig +473 -0
  411. package/src/zig/tests/memory_leak_regression_test.zig +159 -0
  412. package/src/zig/tests/native-span-feed_test.zig +1264 -0
  413. package/src/zig/tests/renderer_test.zig +1017 -0
  414. package/src/zig/tests/rope-nested_test.zig +712 -0
  415. package/src/zig/tests/rope_fuzz_test.zig +238 -0
  416. package/src/zig/tests/rope_test.zig +2362 -0
  417. package/src/zig/tests/segment-merge.test.zig +148 -0
  418. package/src/zig/tests/syntax-style_test.zig +557 -0
  419. package/src/zig/tests/terminal_test.zig +754 -0
  420. package/src/zig/tests/text-buffer-drawing_test.zig +3237 -0
  421. package/src/zig/tests/text-buffer-highlights_test.zig +666 -0
  422. package/src/zig/tests/text-buffer-iterators_test.zig +776 -0
  423. package/src/zig/tests/text-buffer-segment_test.zig +320 -0
  424. package/src/zig/tests/text-buffer-selection_test.zig +1035 -0
  425. package/src/zig/tests/text-buffer-selection_viewport_test.zig +358 -0
  426. package/src/zig/tests/text-buffer-view_test.zig +3649 -0
  427. package/src/zig/tests/text-buffer_test.zig +2191 -0
  428. package/src/zig/tests/unicode-width-map.zon +3909 -0
  429. package/src/zig/tests/utf8_no_zwj_test.zig +260 -0
  430. package/src/zig/tests/utf8_test.zig +4057 -0
  431. package/src/zig/tests/utf8_wcwidth_cursor_test.zig +267 -0
  432. package/src/zig/tests/utf8_wcwidth_test.zig +357 -0
  433. package/src/zig/tests/word-wrap-editing_test.zig +498 -0
  434. package/src/zig/tests/wrap-cache-perf_test.zig +113 -0
  435. package/src/zig/text-buffer-iterators.zig +499 -0
  436. package/src/zig/text-buffer-segment.zig +404 -0
  437. package/src/zig/text-buffer-view.zig +1371 -0
  438. package/src/zig/text-buffer.zig +1180 -0
  439. package/src/zig/utf8.zig +1948 -0
  440. package/src/zig/utils.zig +9 -0
  441. package/src/zig-structs.ts +261 -0
  442. package/src/zig.ts +3884 -0
  443. package/tsconfig.build.json +24 -0
  444. package/tsconfig.json +27 -0
  445. package/3d/SpriteResourceManager.d.ts +0 -74
  446. package/3d/SpriteUtils.d.ts +0 -13
  447. package/3d/TextureUtils.d.ts +0 -24
  448. package/3d/ThreeRenderable.d.ts +0 -40
  449. package/3d/WGPURenderer.d.ts +0 -61
  450. package/3d/animation/ExplodingSpriteEffect.d.ts +0 -71
  451. package/3d/animation/PhysicsExplodingSpriteEffect.d.ts +0 -76
  452. package/3d/animation/SpriteAnimator.d.ts +0 -124
  453. package/3d/animation/SpriteParticleGenerator.d.ts +0 -62
  454. package/3d/canvas.d.ts +0 -44
  455. package/3d/index.d.ts +0 -12
  456. package/3d/physics/PlanckPhysicsAdapter.d.ts +0 -19
  457. package/3d/physics/RapierPhysicsAdapter.d.ts +0 -19
  458. package/3d/physics/physics-interface.d.ts +0 -27
  459. package/3d.d.ts +0 -2
  460. package/3d.js +0 -34041
  461. package/3d.js.map +0 -155
  462. package/LICENSE +0 -21
  463. package/NativeSpanFeed.d.ts +0 -41
  464. package/Renderable.d.ts +0 -334
  465. package/animation/Timeline.d.ts +0 -126
  466. package/ansi.d.ts +0 -13
  467. package/buffer.d.ts +0 -111
  468. package/console.d.ts +0 -144
  469. package/edit-buffer.d.ts +0 -98
  470. package/editor-view.d.ts +0 -73
  471. package/index-8fks7yv1.js +0 -411
  472. package/index-8fks7yv1.js.map +0 -10
  473. package/index-egy5e2rs.js +0 -12267
  474. package/index-egy5e2rs.js.map +0 -42
  475. package/index-tse8gzh0.js +0 -20614
  476. package/index-tse8gzh0.js.map +0 -67
  477. package/index.d.ts +0 -23
  478. package/index.js +0 -478
  479. package/index.js.map +0 -9
  480. package/lib/KeyHandler.d.ts +0 -61
  481. package/lib/RGBA.d.ts +0 -25
  482. package/lib/ascii.font.d.ts +0 -508
  483. package/lib/border.d.ts +0 -51
  484. package/lib/bunfs.d.ts +0 -7
  485. package/lib/clipboard.d.ts +0 -17
  486. package/lib/clock.d.ts +0 -15
  487. package/lib/data-paths.d.ts +0 -26
  488. package/lib/debounce.d.ts +0 -42
  489. package/lib/detect-links.d.ts +0 -6
  490. package/lib/env.d.ts +0 -42
  491. package/lib/extmarks-history.d.ts +0 -17
  492. package/lib/extmarks.d.ts +0 -89
  493. package/lib/hast-styled-text.d.ts +0 -17
  494. package/lib/index.d.ts +0 -21
  495. package/lib/keymapping.d.ts +0 -25
  496. package/lib/objects-in-viewport.d.ts +0 -24
  497. package/lib/output.capture.d.ts +0 -24
  498. package/lib/parse.keypress-kitty.d.ts +0 -2
  499. package/lib/parse.keypress.d.ts +0 -26
  500. package/lib/parse.mouse.d.ts +0 -30
  501. package/lib/paste.d.ts +0 -7
  502. package/lib/queue.d.ts +0 -15
  503. package/lib/renderable.validations.d.ts +0 -12
  504. package/lib/scroll-acceleration.d.ts +0 -43
  505. package/lib/selection.d.ts +0 -63
  506. package/lib/singleton.d.ts +0 -7
  507. package/lib/stdin-parser.d.ts +0 -87
  508. package/lib/styled-text.d.ts +0 -63
  509. package/lib/terminal-capability-detection.d.ts +0 -30
  510. package/lib/terminal-palette.d.ts +0 -50
  511. package/lib/tree-sitter/assets/update.d.ts +0 -11
  512. package/lib/tree-sitter/client.d.ts +0 -47
  513. package/lib/tree-sitter/default-parsers.d.ts +0 -2
  514. package/lib/tree-sitter/download-utils.d.ts +0 -21
  515. package/lib/tree-sitter/index.d.ts +0 -8
  516. package/lib/tree-sitter/parser.worker.d.ts +0 -1
  517. package/lib/tree-sitter/parsers-config.d.ts +0 -53
  518. package/lib/tree-sitter/resolve-ft.d.ts +0 -5
  519. package/lib/tree-sitter/types.d.ts +0 -82
  520. package/lib/tree-sitter-styled-text.d.ts +0 -14
  521. package/lib/validate-dir-name.d.ts +0 -1
  522. package/lib/yoga.options.d.ts +0 -32
  523. package/parser.worker.js +0 -899
  524. package/parser.worker.js.map +0 -12
  525. package/plugins/core-slot.d.ts +0 -72
  526. package/plugins/registry.d.ts +0 -42
  527. package/plugins/types.d.ts +0 -34
  528. package/post/effects.d.ts +0 -147
  529. package/post/filters.d.ts +0 -65
  530. package/post/matrices.d.ts +0 -20
  531. package/renderables/ASCIIFont.d.ts +0 -52
  532. package/renderables/Box.d.ts +0 -81
  533. package/renderables/Code.d.ts +0 -78
  534. package/renderables/Diff.d.ts +0 -142
  535. package/renderables/EditBufferRenderable.d.ts +0 -237
  536. package/renderables/FrameBuffer.d.ts +0 -16
  537. package/renderables/Input.d.ts +0 -67
  538. package/renderables/LineNumberRenderable.d.ts +0 -78
  539. package/renderables/Markdown.d.ts +0 -185
  540. package/renderables/ScrollBar.d.ts +0 -77
  541. package/renderables/ScrollBox.d.ts +0 -124
  542. package/renderables/Select.d.ts +0 -115
  543. package/renderables/Slider.d.ts +0 -47
  544. package/renderables/TabSelect.d.ts +0 -96
  545. package/renderables/Text.d.ts +0 -36
  546. package/renderables/TextBufferRenderable.d.ts +0 -105
  547. package/renderables/TextNode.d.ts +0 -91
  548. package/renderables/TextTable.d.ts +0 -140
  549. package/renderables/Textarea.d.ts +0 -63
  550. package/renderables/TimeToFirstDraw.d.ts +0 -24
  551. package/renderables/__tests__/renderable-test-utils.d.ts +0 -12
  552. package/renderables/composition/VRenderable.d.ts +0 -16
  553. package/renderables/composition/constructs.d.ts +0 -35
  554. package/renderables/composition/vnode.d.ts +0 -46
  555. package/renderables/index.d.ts +0 -23
  556. package/renderables/markdown-parser.d.ts +0 -10
  557. package/renderer.d.ts +0 -419
  558. package/runtime-plugin-support.d.ts +0 -3
  559. package/runtime-plugin-support.js +0 -29
  560. package/runtime-plugin-support.js.map +0 -10
  561. package/runtime-plugin.d.ts +0 -16
  562. package/runtime-plugin.js +0 -16
  563. package/runtime-plugin.js.map +0 -9
  564. package/syntax-style.d.ts +0 -54
  565. package/testing/manual-clock.d.ts +0 -17
  566. package/testing/mock-keys.d.ts +0 -81
  567. package/testing/mock-mouse.d.ts +0 -38
  568. package/testing/mock-tree-sitter-client.d.ts +0 -23
  569. package/testing/spy.d.ts +0 -7
  570. package/testing/test-recorder.d.ts +0 -61
  571. package/testing/test-renderer.d.ts +0 -23
  572. package/testing.d.ts +0 -6
  573. package/testing.js +0 -697
  574. package/testing.js.map +0 -15
  575. package/text-buffer-view.d.ts +0 -42
  576. package/text-buffer.d.ts +0 -67
  577. package/types.d.ts +0 -139
  578. package/utils.d.ts +0 -14
  579. package/zig-structs.d.ts +0 -155
  580. package/zig.d.ts +0 -353
  581. /package/{assets → src/lib/tree-sitter/assets}/javascript/highlights.scm +0 -0
  582. /package/{assets → src/lib/tree-sitter/assets}/javascript/tree-sitter-javascript.wasm +0 -0
  583. /package/{assets → src/lib/tree-sitter/assets}/markdown/highlights.scm +0 -0
  584. /package/{assets → src/lib/tree-sitter/assets}/markdown/injections.scm +0 -0
  585. /package/{assets → src/lib/tree-sitter/assets}/markdown/tree-sitter-markdown.wasm +0 -0
  586. /package/{assets → src/lib/tree-sitter/assets}/markdown_inline/highlights.scm +0 -0
  587. /package/{assets → src/lib/tree-sitter/assets}/markdown_inline/tree-sitter-markdown_inline.wasm +0 -0
  588. /package/{assets → src/lib/tree-sitter/assets}/typescript/highlights.scm +0 -0
  589. /package/{assets → src/lib/tree-sitter/assets}/typescript/tree-sitter-typescript.wasm +0 -0
  590. /package/{assets → src/lib/tree-sitter/assets}/zig/highlights.scm +0 -0
  591. /package/{assets → src/lib/tree-sitter/assets}/zig/tree-sitter-zig.wasm +0 -0
@@ -0,0 +1,825 @@
1
+ const std = @import("std");
2
+ const Allocator = std.mem.Allocator;
3
+ const tb = @import("text-buffer.zig");
4
+ const iter_mod = @import("text-buffer-iterators.zig");
5
+ const seg_mod = @import("text-buffer-segment.zig");
6
+ const gp = @import("grapheme.zig");
7
+ const link = @import("link.zig");
8
+
9
+ const utf8 = @import("utf8.zig");
10
+ const event_emitter = @import("event-emitter.zig");
11
+ const event_bus = @import("event-bus.zig");
12
+
13
+ const UnifiedTextBuffer = tb.UnifiedTextBuffer;
14
+ const TextChunk = seg_mod.TextChunk;
15
+ const Segment = seg_mod.Segment;
16
+ const UnifiedRope = seg_mod.UnifiedRope;
17
+
18
+ var global_edit_buffer_id: u16 = 0;
19
+
20
+ pub const EditBufferError = error{
21
+ OutOfMemory,
22
+ InvalidCursor,
23
+ };
24
+
25
+ pub const EditBufferEvent = enum {
26
+ cursorChanged,
27
+ };
28
+
29
+ /// Cursor position (row, col in display-width coordinates)
30
+ pub const Cursor = struct {
31
+ row: u32,
32
+ col: u32,
33
+ desired_col: u32 = 0,
34
+ offset: u32 = 0, // Global display-width offset from buffer start
35
+ };
36
+
37
+ const CursorCoords = struct { row: u32, col: u32 };
38
+
39
+ const AddBuffer = struct {
40
+ mem_id: u8,
41
+ ptr: [*]u8,
42
+ len: usize,
43
+ cap: usize,
44
+ allocator: Allocator,
45
+
46
+ fn init(allocator: Allocator, text_buffer: *UnifiedTextBuffer, initial_cap: usize) !AddBuffer {
47
+ const mem = try allocator.alloc(u8, initial_cap);
48
+ const mem_id = try text_buffer.registerMemBuffer(mem, true);
49
+
50
+ return .{
51
+ .mem_id = mem_id,
52
+ .ptr = mem.ptr,
53
+ .len = 0,
54
+ .cap = mem.len,
55
+ .allocator = allocator,
56
+ };
57
+ }
58
+
59
+ fn ensureCapacity(self: *AddBuffer, text_buffer: *UnifiedTextBuffer, need: usize) !void {
60
+ if (self.len + need <= self.cap) return;
61
+
62
+ // TODO: Create a new buffer, register the new buffer and use the new mem_id for subsequent inserts
63
+ const new_cap = @max(self.cap * 2, self.len + need);
64
+ const new_mem = try self.allocator.alloc(u8, new_cap);
65
+ const new_mem_id = try text_buffer.registerMemBuffer(new_mem, true);
66
+ self.mem_id = new_mem_id;
67
+ self.ptr = new_mem.ptr;
68
+ self.len = 0;
69
+ self.cap = new_mem.len;
70
+ }
71
+
72
+ fn append(self: *AddBuffer, bytes: []const u8) struct { mem_id: u8, start: u32, end: u32 } {
73
+ std.debug.assert(self.len + bytes.len <= self.cap);
74
+ const start: u32 = @intCast(self.len);
75
+
76
+ const dest_slice = self.ptr[0..self.cap];
77
+ @memcpy(dest_slice[self.len .. self.len + bytes.len], bytes);
78
+
79
+ self.len += bytes.len;
80
+ const end: u32 = @intCast(self.len);
81
+ return .{ .mem_id = self.mem_id, .start = start, .end = end };
82
+ }
83
+ };
84
+
85
+ pub const EditBuffer = struct {
86
+ id: u16,
87
+ tb: *UnifiedTextBuffer,
88
+ add_buffer: AddBuffer,
89
+ cursors: std.ArrayListUnmanaged(Cursor),
90
+ allocator: Allocator,
91
+ events: event_emitter.EventEmitter(EditBufferEvent),
92
+ segment_splitter: UnifiedRope.Node.LeafSplitFn,
93
+
94
+ pub fn init(
95
+ allocator: Allocator,
96
+ pool: *gp.GraphemePool,
97
+ link_pool: *link.LinkPool,
98
+ width_method: utf8.WidthMethod,
99
+ ) !*EditBuffer {
100
+ const self = try allocator.create(EditBuffer);
101
+ errdefer allocator.destroy(self);
102
+
103
+ const text_buffer = try UnifiedTextBuffer.init(allocator, pool, link_pool, width_method);
104
+ errdefer text_buffer.deinit();
105
+
106
+ const add_buffer = try AddBuffer.init(allocator, text_buffer, 65536);
107
+ errdefer {}
108
+
109
+ var cursors: std.ArrayListUnmanaged(Cursor) = .{};
110
+ errdefer cursors.deinit(allocator);
111
+
112
+ try cursors.append(allocator, .{ .row = 0, .col = 0 });
113
+
114
+ const buffer_id = global_edit_buffer_id;
115
+ global_edit_buffer_id += 1;
116
+
117
+ self.* = .{
118
+ .id = buffer_id,
119
+ .tb = text_buffer,
120
+ .add_buffer = add_buffer,
121
+ .cursors = cursors,
122
+ .allocator = allocator,
123
+ .events = event_emitter.EventEmitter(EditBufferEvent).init(allocator),
124
+ .segment_splitter = .{ .ctx = self, .splitFn = splitSegmentCallback },
125
+ };
126
+
127
+ return self;
128
+ }
129
+
130
+ pub fn deinit(self: *EditBuffer) void {
131
+ // Registry owns all AddBuffer memory, don't free it manually
132
+ self.events.deinit();
133
+ self.tb.deinit();
134
+ self.cursors.deinit(self.allocator);
135
+ self.allocator.destroy(self);
136
+ }
137
+
138
+ pub fn getId(self: *const EditBuffer) u16 {
139
+ return self.id;
140
+ }
141
+
142
+ fn emitNativeEvent(self: *const EditBuffer, event_name: []const u8) void {
143
+ var id_bytes: [2]u8 = undefined;
144
+ std.mem.writeInt(u16, &id_bytes, self.id, .little);
145
+
146
+ const full_name = std.fmt.allocPrint(self.allocator, "eb_{s}", .{event_name}) catch return;
147
+ defer self.allocator.free(full_name);
148
+
149
+ event_bus.emit(full_name, &id_bytes);
150
+ }
151
+
152
+ pub fn getTextBuffer(self: *EditBuffer) *UnifiedTextBuffer {
153
+ return self.tb;
154
+ }
155
+
156
+ pub fn getCursor(self: *const EditBuffer, idx: usize) ?Cursor {
157
+ if (idx >= self.cursors.items.len) return null;
158
+ return self.cursors.items[idx];
159
+ }
160
+
161
+ pub fn getPrimaryCursor(self: *const EditBuffer) Cursor {
162
+ if (self.cursors.items.len == 0) return .{ .row = 0, .col = 0 };
163
+ return self.cursors.items[0];
164
+ }
165
+
166
+ pub fn setCursor(self: *EditBuffer, row: u32, col: u32) !void {
167
+ const line_count = self.tb.lineCount();
168
+ const clamped_row = @min(row, line_count -| 1);
169
+
170
+ const line_width = iter_mod.lineWidthAt(self.tb.rope(), clamped_row);
171
+ const clamped_col = @min(col, line_width);
172
+
173
+ const offset = iter_mod.coordsToOffset(self.tb.rope(), clamped_row, clamped_col) orelse 0;
174
+
175
+ if (self.cursors.items.len == 0) {
176
+ try self.cursors.append(self.allocator, .{ .row = clamped_row, .col = clamped_col, .desired_col = clamped_col, .offset = offset });
177
+ } else {
178
+ self.cursors.items[0] = .{ .row = clamped_row, .col = clamped_col, .desired_col = clamped_col, .offset = offset };
179
+ }
180
+
181
+ self.events.emit(.cursorChanged);
182
+ self.emitNativeEvent("cursor-changed");
183
+ }
184
+
185
+ pub fn setCursorByOffset(self: *EditBuffer, offset: u32) !void {
186
+ const coords = iter_mod.offsetToCoords(self.tb.rope(), offset) orelse iter_mod.Coords{ .row = 0, .col = 0 };
187
+ try self.setCursor(coords.row, coords.col);
188
+ }
189
+
190
+ fn ensureAddCapacity(self: *EditBuffer, need: usize) !void {
191
+ try self.add_buffer.ensureCapacity(self.tb, need);
192
+ }
193
+
194
+ /// TODO: This method should live in text-buffer-segment.zig and the Rope should take it as comptime param
195
+ fn splitChunkAtWeight(
196
+ self: *EditBuffer,
197
+ chunk: *const TextChunk,
198
+ weight: u32,
199
+ ) error{ OutOfBounds, OutOfMemory }!struct { left: TextChunk, right: TextChunk } {
200
+ const chunk_weight = chunk.width;
201
+
202
+ if (weight == 0) {
203
+ return .{
204
+ .left = TextChunk{ .mem_id = 0, .byte_start = 0, .byte_end = 0, .width = 0 },
205
+ .right = chunk.*,
206
+ };
207
+ } else if (weight >= chunk_weight) {
208
+ return .{
209
+ .left = chunk.*,
210
+ .right = TextChunk{ .mem_id = 0, .byte_start = 0, .byte_end = 0, .width = 0 },
211
+ };
212
+ }
213
+
214
+ const chunk_bytes = chunk.getBytes(self.tb.memRegistry());
215
+ const is_ascii_only = (chunk.flags & TextChunk.Flags.ASCII_ONLY) != 0;
216
+
217
+ const result = utf8.findPosByWidth(chunk_bytes, weight, self.tb.tabWidth(), is_ascii_only, false, self.tb.widthMethod());
218
+ const split_byte_offset = result.byte_offset;
219
+
220
+ const left_chunk = self.tb.createChunk(
221
+ chunk.mem_id,
222
+ chunk.byte_start,
223
+ chunk.byte_start + split_byte_offset,
224
+ );
225
+
226
+ const right_chunk = self.tb.createChunk(
227
+ chunk.mem_id,
228
+ chunk.byte_start + split_byte_offset,
229
+ chunk.byte_end,
230
+ );
231
+
232
+ return .{ .left = left_chunk, .right = right_chunk };
233
+ }
234
+
235
+ fn splitSegmentCallback(
236
+ ctx: ?*anyopaque,
237
+ allocator: Allocator,
238
+ leaf: *const Segment,
239
+ weight_in_leaf: u32,
240
+ ) error{ OutOfBounds, OutOfMemory }!UnifiedRope.Node.LeafSplitResult {
241
+ _ = allocator;
242
+ const edit_buf = @as(*EditBuffer, @ptrCast(@alignCast(ctx.?)));
243
+
244
+ if (leaf.asText()) |chunk| {
245
+ const result = try edit_buf.splitChunkAtWeight(chunk, weight_in_leaf);
246
+ return .{
247
+ .left = Segment{ .text = result.left },
248
+ .right = Segment{ .text = result.right },
249
+ };
250
+ } else {
251
+ return .{
252
+ .left = Segment{ .brk = {} },
253
+ .right = Segment{ .brk = {} },
254
+ };
255
+ }
256
+ }
257
+
258
+ pub fn insertText(self: *EditBuffer, bytes: []const u8) !void {
259
+ if (bytes.len == 0) return;
260
+ if (self.cursors.items.len == 0) return;
261
+
262
+ try self.autoStoreUndo();
263
+
264
+ const cursor = self.cursors.items[0];
265
+
266
+ try self.ensureAddCapacity(bytes.len);
267
+
268
+ const insert_offset = iter_mod.coordsToOffset(self.tb.rope(), cursor.row, cursor.col) orelse return EditBufferError.InvalidCursor;
269
+
270
+ const chunk_ref = self.add_buffer.append(bytes);
271
+ const base_mem_id = chunk_ref.mem_id;
272
+ const base_start = chunk_ref.start;
273
+
274
+ var result = try self.tb.textToSegments(self.allocator, bytes, base_mem_id, base_start, false);
275
+ defer result.segments.deinit(result.allocator);
276
+
277
+ const inserted_width = result.total_width;
278
+
279
+ // Calculate width after last break
280
+ var width_after_last_break: u32 = 0;
281
+ var num_breaks: usize = 0;
282
+ for (result.segments.items) |seg| {
283
+ if (seg.isBreak()) {
284
+ num_breaks += 1;
285
+ width_after_last_break = 0;
286
+ } else if (seg.asText()) |chunk| {
287
+ width_after_last_break += chunk.width;
288
+ }
289
+ }
290
+
291
+ if (result.segments.items.len > 0) {
292
+ try self.tb.rope().insertSliceByWeight(insert_offset, result.segments.items, &self.segment_splitter);
293
+ }
294
+ if (num_breaks > 0) {
295
+ const new_row = cursor.row + @as(u32, @intCast(num_breaks));
296
+ const new_col = width_after_last_break;
297
+ const new_offset = iter_mod.coordsToOffset(self.tb.rope(), new_row, new_col) orelse 0;
298
+ self.cursors.items[0] = .{
299
+ .row = new_row,
300
+ .col = new_col,
301
+ .desired_col = new_col,
302
+ .offset = new_offset,
303
+ };
304
+ } else {
305
+ const new_col = cursor.col + inserted_width;
306
+ const new_offset = iter_mod.coordsToOffset(self.tb.rope(), cursor.row, new_col) orelse 0;
307
+ self.cursors.items[0] = .{
308
+ .row = cursor.row,
309
+ .col = new_col,
310
+ .desired_col = new_col,
311
+ .offset = new_offset,
312
+ };
313
+ }
314
+
315
+ self.tb.markViewsDirty();
316
+ self.events.emit(.cursorChanged);
317
+ self.emitNativeEvent("cursor-changed");
318
+ self.emitNativeEvent("content-changed");
319
+ }
320
+
321
+ pub fn deleteRange(self: *EditBuffer, start_cursor: Cursor, end_cursor: Cursor) !void {
322
+ var start = start_cursor;
323
+ var end = end_cursor;
324
+ if (start.row > end.row or (start.row == end.row and start.col > end.col)) {
325
+ const temp = start;
326
+ start = end;
327
+ end = temp;
328
+ }
329
+
330
+ if (start.row == end.row and start.col == end.col) return;
331
+
332
+ try self.autoStoreUndo();
333
+
334
+ const start_offset = iter_mod.coordsToOffset(self.tb.rope(), start.row, start.col) orelse return EditBufferError.InvalidCursor;
335
+ const end_offset = iter_mod.coordsToOffset(self.tb.rope(), end.row, end.col) orelse return EditBufferError.InvalidCursor;
336
+
337
+ if (start_offset >= end_offset) return;
338
+
339
+ try self.tb.rope().deleteRangeByWeight(start_offset, end_offset, &self.segment_splitter);
340
+
341
+ self.tb.markViewsDirty();
342
+
343
+ if (self.cursors.items.len > 0) {
344
+ const line_count = self.tb.lineCount();
345
+ const clamped_row = if (start.row >= line_count) line_count -| 1 else start.row;
346
+ const line_width = if (line_count > 0) iter_mod.lineWidthAt(self.tb.rope(), clamped_row) else 0;
347
+ const clamped_col = @min(start.col, line_width);
348
+ const offset = iter_mod.coordsToOffset(self.tb.rope(), clamped_row, clamped_col) orelse 0;
349
+
350
+ self.cursors.items[0] = .{ .row = clamped_row, .col = clamped_col, .desired_col = clamped_col, .offset = offset };
351
+ }
352
+
353
+ self.events.emit(.cursorChanged);
354
+ self.emitNativeEvent("cursor-changed");
355
+ self.emitNativeEvent("content-changed");
356
+ }
357
+
358
+ pub fn backspace(self: *EditBuffer) !void {
359
+ if (self.cursors.items.len == 0) return;
360
+ const cursor = self.cursors.items[0];
361
+
362
+ if (cursor.row == 0 and cursor.col == 0) return;
363
+
364
+ if (cursor.col == 0) {
365
+ if (cursor.row > 0) {
366
+ const prev_line_width = iter_mod.lineWidthAt(self.tb.rope(), cursor.row - 1);
367
+ try self.deleteRange(
368
+ .{ .row = cursor.row - 1, .col = prev_line_width },
369
+ .{ .row = cursor.row, .col = 0 },
370
+ );
371
+ }
372
+ } else {
373
+ const prev_grapheme_width = self.tb.getPrevGraphemeWidth(cursor.row, cursor.col);
374
+ if (prev_grapheme_width == 0) return; // Nothing to delete
375
+
376
+ const target_col = cursor.col - prev_grapheme_width;
377
+ try self.deleteRange(
378
+ .{ .row = cursor.row, .col = target_col },
379
+ .{ .row = cursor.row, .col = cursor.col },
380
+ );
381
+ }
382
+
383
+ // deleteRange already checks for placeholder insertion
384
+ }
385
+
386
+ pub fn deleteForward(self: *EditBuffer) !void {
387
+ if (self.cursors.items.len == 0) return;
388
+ const cursor = self.cursors.items[0];
389
+
390
+ try self.autoStoreUndo();
391
+
392
+ const line_width = iter_mod.lineWidthAt(self.tb.rope(), cursor.row);
393
+ const line_count = self.tb.lineCount();
394
+
395
+ if (cursor.col >= line_width) {
396
+ if (cursor.row + 1 < line_count) {
397
+ try self.deleteRange(
398
+ .{ .row = cursor.row, .col = line_width },
399
+ .{ .row = cursor.row + 1, .col = 0 },
400
+ );
401
+ }
402
+ } else {
403
+ const grapheme_width = self.tb.getGraphemeWidthAt(cursor.row, cursor.col);
404
+ if (grapheme_width > 0) {
405
+ try self.deleteRange(
406
+ .{ .row = cursor.row, .col = cursor.col },
407
+ .{ .row = cursor.row, .col = cursor.col + grapheme_width },
408
+ );
409
+ }
410
+ }
411
+ }
412
+
413
+ pub fn moveLeft(self: *EditBuffer) void {
414
+ if (self.cursors.items.len == 0) {
415
+ return;
416
+ }
417
+ const cursor = &self.cursors.items[0];
418
+
419
+ if (cursor.col > 0) {
420
+ const prev_width = self.tb.getPrevGraphemeWidth(cursor.row, cursor.col);
421
+ cursor.col -= prev_width;
422
+ } else if (cursor.row > 0) {
423
+ cursor.row -= 1;
424
+ const line_width = iter_mod.lineWidthAt(self.tb.rope(), cursor.row);
425
+ cursor.col = line_width;
426
+ }
427
+ cursor.desired_col = cursor.col;
428
+ cursor.offset = iter_mod.coordsToOffset(self.tb.rope(), cursor.row, cursor.col) orelse 0;
429
+
430
+ self.events.emit(.cursorChanged);
431
+ self.emitNativeEvent("cursor-changed");
432
+ }
433
+
434
+ pub fn moveRight(self: *EditBuffer) void {
435
+ if (self.cursors.items.len == 0) return;
436
+ const cursor = &self.cursors.items[0];
437
+
438
+ const line_width = iter_mod.lineWidthAt(self.tb.rope(), cursor.row);
439
+ const line_count = self.tb.getLineCount();
440
+
441
+ if (cursor.col < line_width) {
442
+ const grapheme_width = self.tb.getGraphemeWidthAt(cursor.row, cursor.col);
443
+ cursor.col += grapheme_width;
444
+ } else if (cursor.row + 1 < line_count) {
445
+ cursor.row += 1;
446
+ cursor.col = 0;
447
+ }
448
+ cursor.desired_col = cursor.col;
449
+ cursor.offset = iter_mod.coordsToOffset(self.tb.rope(), cursor.row, cursor.col) orelse 0;
450
+
451
+ self.events.emit(.cursorChanged);
452
+ self.emitNativeEvent("cursor-changed");
453
+ }
454
+
455
+ pub fn moveUp(self: *EditBuffer) void {
456
+ if (self.cursors.items.len == 0) return;
457
+ const cursor = &self.cursors.items[0];
458
+
459
+ if (cursor.row > 0) {
460
+ if (cursor.desired_col == 0) {
461
+ cursor.desired_col = cursor.col;
462
+ }
463
+
464
+ cursor.row -= 1;
465
+
466
+ const line_width = iter_mod.lineWidthAt(self.tb.rope(), cursor.row);
467
+
468
+ cursor.col = @min(cursor.desired_col, line_width);
469
+ cursor.offset = iter_mod.coordsToOffset(self.tb.rope(), cursor.row, cursor.col) orelse 0;
470
+ }
471
+
472
+ self.events.emit(.cursorChanged);
473
+ self.emitNativeEvent("cursor-changed");
474
+ }
475
+
476
+ pub fn moveDown(self: *EditBuffer) void {
477
+ if (self.cursors.items.len == 0) return;
478
+ const cursor = &self.cursors.items[0];
479
+
480
+ const line_count = self.tb.getLineCount();
481
+ if (cursor.row + 1 < line_count) {
482
+ if (cursor.desired_col == 0) {
483
+ cursor.desired_col = cursor.col;
484
+ }
485
+
486
+ cursor.row += 1;
487
+
488
+ const line_width = iter_mod.lineWidthAt(self.tb.rope(), cursor.row);
489
+
490
+ cursor.col = @min(cursor.desired_col, line_width);
491
+ cursor.offset = iter_mod.coordsToOffset(self.tb.rope(), cursor.row, cursor.col) orelse 0;
492
+ }
493
+
494
+ self.events.emit(.cursorChanged);
495
+ self.emitNativeEvent("cursor-changed");
496
+ }
497
+
498
+ /// Set text and completely reset the buffer state (clears history, resets add_buffer)
499
+ pub fn setText(self: *EditBuffer, text: []const u8) !void {
500
+ const owned_text = try self.allocator.dupe(u8, text);
501
+ const mem_id = try self.tb.registerMemBuffer(owned_text, true);
502
+ try self.setTextFromMemId(mem_id);
503
+ }
504
+
505
+ /// Set text from memory ID and completely reset the buffer state (clears history, resets add_buffer)
506
+ pub fn setTextFromMemId(self: *EditBuffer, mem_id: u8) !void {
507
+ self.tb.rope().clear_history();
508
+ self.add_buffer.len = 0;
509
+
510
+ try self.tb.setTextFromMemId(mem_id);
511
+ try self.setCursor(0, 0);
512
+
513
+ self.emitNativeEvent("content-changed");
514
+ }
515
+
516
+ /// Replace text while preserving undo history (creates an undo point)
517
+ pub fn replaceText(self: *EditBuffer, text: []const u8) !void {
518
+ const owned_text = try self.allocator.dupe(u8, text);
519
+ const mem_id = try self.tb.registerMemBuffer(owned_text, true);
520
+ try self.replaceTextFromMemId(mem_id);
521
+ }
522
+
523
+ /// Replace text from memory ID while preserving undo history (creates an undo point)
524
+ pub fn replaceTextFromMemId(self: *EditBuffer, mem_id: u8) !void {
525
+ try self.autoStoreUndo();
526
+
527
+ try self.tb.setTextFromMemId(mem_id);
528
+ try self.setCursor(0, 0);
529
+
530
+ self.emitNativeEvent("content-changed");
531
+ }
532
+
533
+ pub fn getText(self: *EditBuffer, out_buffer: []u8) usize {
534
+ return self.tb.getPlainTextIntoBuffer(out_buffer);
535
+ }
536
+
537
+ pub fn deleteLine(self: *EditBuffer) !void {
538
+ const cursor = self.getPrimaryCursor();
539
+ const line_count = self.tb.lineCount();
540
+
541
+ if (cursor.row >= line_count) return;
542
+
543
+ if (cursor.row + 1 < line_count) {
544
+ try self.deleteRange(
545
+ .{ .row = cursor.row, .col = 0 },
546
+ .{ .row = cursor.row + 1, .col = 0 },
547
+ );
548
+ } else if (cursor.row > 0) {
549
+ const prev_line_width = iter_mod.lineWidthAt(self.tb.rope(), cursor.row - 1);
550
+ const curr_line_width = iter_mod.lineWidthAt(self.tb.rope(), cursor.row);
551
+
552
+ try self.deleteRange(
553
+ .{ .row = cursor.row - 1, .col = prev_line_width },
554
+ .{ .row = cursor.row, .col = curr_line_width },
555
+ );
556
+
557
+ self.tb.markViewsDirty();
558
+
559
+ const new_row = cursor.row - 1;
560
+ const new_col = prev_line_width;
561
+ const new_offset = iter_mod.coordsToOffset(self.tb.rope(), new_row, new_col) orelse 0;
562
+ self.cursors.items[0] = .{ .row = new_row, .col = new_col, .desired_col = new_col, .offset = new_offset };
563
+ self.events.emit(.cursorChanged);
564
+ self.emitNativeEvent("cursor-changed");
565
+ } else {
566
+ const line_width = iter_mod.lineWidthAt(self.tb.rope(), cursor.row);
567
+ if (line_width > 0) {
568
+ try self.deleteRange(
569
+ .{ .row = cursor.row, .col = 0 },
570
+ .{ .row = cursor.row, .col = line_width },
571
+ );
572
+ }
573
+ }
574
+ }
575
+
576
+ pub fn gotoLine(self: *EditBuffer, line: u32) !void {
577
+ const line_count = self.tb.lineCount();
578
+ const target_line = @min(line, line_count -| 1);
579
+
580
+ if (line >= line_count) {
581
+ const last_line_width = iter_mod.lineWidthAt(self.tb.rope(), target_line);
582
+ try self.setCursor(target_line, last_line_width);
583
+ } else {
584
+ try self.setCursor(target_line, 0);
585
+ }
586
+ }
587
+
588
+ pub fn getCursorPosition(self: *const EditBuffer) struct { line: u32, visual_col: u32, offset: u32 } {
589
+ const cursor = self.getPrimaryCursor();
590
+
591
+ return .{
592
+ .line = cursor.row,
593
+ .visual_col = cursor.col,
594
+ .offset = cursor.offset,
595
+ };
596
+ }
597
+
598
+ pub fn debugLogRope(self: *const EditBuffer) void {
599
+ self.tb.debugLogRope();
600
+ }
601
+
602
+ fn autoStoreUndo(self: *EditBuffer) !void {
603
+ try self.tb.rope().store_undo("edit");
604
+ }
605
+
606
+ pub fn undo(self: *EditBuffer) ![]const u8 {
607
+ const prev_meta = try self.tb.rope().undo("current");
608
+
609
+ const cursor = self.getPrimaryCursor();
610
+ try self.setCursor(cursor.row, cursor.col);
611
+
612
+ self.tb.markViewsDirty();
613
+ self.events.emit(.cursorChanged);
614
+ self.emitNativeEvent("cursorChanged");
615
+
616
+ return prev_meta;
617
+ }
618
+
619
+ pub fn redo(self: *EditBuffer) ![]const u8 {
620
+ const next_meta = try self.tb.rope().redo();
621
+
622
+ const cursor = self.getPrimaryCursor();
623
+ try self.setCursor(cursor.row, cursor.col);
624
+
625
+ self.tb.markViewsDirty();
626
+ self.events.emit(.cursorChanged);
627
+ self.emitNativeEvent("cursorChanged");
628
+
629
+ return next_meta;
630
+ }
631
+
632
+ pub fn canUndo(self: *const EditBuffer) bool {
633
+ return self.tb.rope().can_undo();
634
+ }
635
+
636
+ pub fn canRedo(self: *const EditBuffer) bool {
637
+ return self.tb.rope().can_redo();
638
+ }
639
+
640
+ pub fn clearHistory(self: *EditBuffer) void {
641
+ self.tb.rope().clear_history();
642
+ }
643
+
644
+ pub fn clear(self: *EditBuffer) !void {
645
+ self.tb.clear();
646
+ try self.setCursor(0, 0);
647
+ self.emitNativeEvent("content-changed");
648
+ }
649
+
650
+ pub fn getNextWordBoundary(self: *EditBuffer) Cursor {
651
+ if (self.cursors.items.len == 0) return .{ .row = 0, .col = 0 };
652
+ const cursor = self.cursors.items[0];
653
+
654
+ const line_count = self.tb.lineCount();
655
+ if (cursor.row >= line_count) return cursor;
656
+
657
+ const line_width = iter_mod.lineWidthAt(self.tb.rope(), cursor.row);
658
+
659
+ const linestart = self.tb.rope().getMarker(.linestart, cursor.row) orelse return cursor;
660
+ var seg_idx = linestart.leaf_index + 1;
661
+ var cols_before: u32 = 0;
662
+ var passed_cursor = false;
663
+
664
+ while (seg_idx < self.tb.rope().count()) : (seg_idx += 1) {
665
+ const seg = self.tb.rope().get(seg_idx) orelse break;
666
+ if (seg.isBreak() or seg.isLineStart()) break;
667
+ if (seg.asText()) |chunk| {
668
+ const next_cols = cols_before + chunk.width;
669
+
670
+ // Check this chunk if cursor is within it OR if we've already passed the cursor
671
+ if (cursor.col < next_cols or passed_cursor) {
672
+ const wrap_offsets = self.tb.getWrapOffsetsFor(chunk) catch {
673
+ cols_before = next_cols;
674
+ passed_cursor = true;
675
+ continue;
676
+ };
677
+ const is_ascii_only = (chunk.flags & TextChunk.Flags.ASCII_ONLY) != 0;
678
+ const graphemes: []const seg_mod.GraphemeInfo = if (is_ascii_only)
679
+ &[_]seg_mod.GraphemeInfo{}
680
+ else
681
+ chunk.getGraphemes(self.tb.memRegistry(), self.tb.getAllocator(), self.tb.tabWidth(), self.tb.widthMethod()) catch &[_]seg_mod.GraphemeInfo{};
682
+ var grapheme_idx: usize = 0;
683
+ var col_delta: i64 = 0;
684
+
685
+ // For chunks containing or after the cursor, find the first break after cursor position
686
+ const local_cursor_col = if (cursor.col > cols_before) cursor.col - cols_before else 0;
687
+
688
+ for (wrap_offsets) |wrap_break| {
689
+ const break_info = iter_mod.charOffsetToColumn(wrap_break.char_offset, graphemes, &grapheme_idx, &col_delta);
690
+ const break_col = break_info.col;
691
+
692
+ // If we've passed the cursor chunk, any break is valid
693
+ // If we're in the cursor chunk, break must be after cursor position
694
+ if (passed_cursor or break_col > local_cursor_col) {
695
+ // break_col points at the break grapheme start.
696
+ // Adding width moves the cursor to the boundary after it.
697
+ const target_col = cols_before + break_col + break_info.width;
698
+ if (target_col <= line_width) {
699
+ const offset = iter_mod.coordsToOffset(self.tb.rope(), cursor.row, target_col) orelse cursor.offset;
700
+ return .{ .row = cursor.row, .col = target_col, .desired_col = target_col, .offset = offset };
701
+ }
702
+ }
703
+
704
+ // A boundary at the cursor can still be the next word step
705
+ // for script-transition cases like "a日", "日a", or "丽abc".
706
+ // Only accept it when the boundary starts on a word codepoint.
707
+ if (!passed_cursor and break_col == local_cursor_col) {
708
+ const break_byte_offset: usize = @intCast(wrap_break.byte_offset);
709
+ const chunk_bytes = chunk.getBytes(self.tb.memRegistry());
710
+ if (break_byte_offset < chunk_bytes.len) {
711
+ const break_cp = utf8.decodeUtf8Unchecked(chunk_bytes, break_byte_offset).cp;
712
+ if (utf8.isWordCodepoint(break_cp)) {
713
+ const target_col = cols_before + break_col + break_info.width;
714
+ if (target_col <= line_width) {
715
+ const offset = iter_mod.coordsToOffset(self.tb.rope(), cursor.row, target_col) orelse cursor.offset;
716
+ return .{ .row = cursor.row, .col = target_col, .desired_col = target_col, .offset = offset };
717
+ }
718
+ }
719
+ }
720
+ }
721
+ }
722
+
723
+ // Mark that we've processed/passed the cursor position
724
+ passed_cursor = true;
725
+ }
726
+ cols_before = next_cols;
727
+ }
728
+ }
729
+
730
+ if (cursor.row + 1 < line_count) {
731
+ const offset = iter_mod.coordsToOffset(self.tb.rope(), cursor.row + 1, 0) orelse cursor.offset;
732
+ return .{ .row = cursor.row + 1, .col = 0, .desired_col = 0, .offset = offset };
733
+ }
734
+
735
+ const offset = iter_mod.coordsToOffset(self.tb.rope(), cursor.row, line_width) orelse cursor.offset;
736
+ return .{ .row = cursor.row, .col = line_width, .desired_col = line_width, .offset = offset };
737
+ }
738
+
739
+ pub fn getPrevWordBoundary(self: *EditBuffer) Cursor {
740
+ if (self.cursors.items.len == 0) return .{ .row = 0, .col = 0 };
741
+ const cursor = self.cursors.items[0];
742
+
743
+ if (cursor.row == 0 and cursor.col == 0) return cursor;
744
+
745
+ const linestart = self.tb.rope().getMarker(.linestart, cursor.row) orelse return cursor;
746
+ var seg_idx = linestart.leaf_index + 1;
747
+ var cols_before: u32 = 0;
748
+ var last_boundary: ?u32 = null;
749
+
750
+ while (seg_idx < self.tb.rope().count()) : (seg_idx += 1) {
751
+ const seg = self.tb.rope().get(seg_idx) orelse break;
752
+ if (seg.isBreak() or seg.isLineStart()) break;
753
+ if (seg.asText()) |chunk| {
754
+ const next_cols = cols_before + chunk.width;
755
+
756
+ const wrap_offsets = self.tb.getWrapOffsetsFor(chunk) catch {
757
+ cols_before = next_cols;
758
+ continue;
759
+ };
760
+ const is_ascii_only = (chunk.flags & TextChunk.Flags.ASCII_ONLY) != 0;
761
+ const graphemes: []const seg_mod.GraphemeInfo = if (is_ascii_only)
762
+ &[_]seg_mod.GraphemeInfo{}
763
+ else
764
+ chunk.getGraphemes(self.tb.memRegistry(), self.tb.getAllocator(), self.tb.tabWidth(), self.tb.widthMethod()) catch &[_]seg_mod.GraphemeInfo{};
765
+ var grapheme_idx: usize = 0;
766
+ var col_delta: i64 = 0;
767
+
768
+ for (wrap_offsets) |wrap_break| {
769
+ const break_info = iter_mod.charOffsetToColumn(wrap_break.char_offset, graphemes, &grapheme_idx, &col_delta);
770
+ // break_info follows the same convention as getNextWordBoundary:
771
+ // use break start + grapheme width to land after the break grapheme.
772
+ const boundary_col = cols_before + break_info.col + break_info.width;
773
+ if (boundary_col < cursor.col) {
774
+ last_boundary = boundary_col;
775
+ }
776
+ }
777
+
778
+ cols_before = next_cols;
779
+ if (cursor.col <= cols_before) break;
780
+ }
781
+ }
782
+
783
+ if (last_boundary) |boundary_col| {
784
+ const offset = iter_mod.coordsToOffset(self.tb.rope(), cursor.row, boundary_col) orelse cursor.offset;
785
+ return .{ .row = cursor.row, .col = boundary_col, .desired_col = boundary_col, .offset = offset };
786
+ }
787
+
788
+ if (cursor.row > 0) {
789
+ const prev_line_width = iter_mod.lineWidthAt(self.tb.rope(), cursor.row - 1);
790
+ const offset = iter_mod.coordsToOffset(self.tb.rope(), cursor.row - 1, prev_line_width) orelse cursor.offset;
791
+ return .{ .row = cursor.row - 1, .col = prev_line_width, .desired_col = prev_line_width, .offset = offset };
792
+ }
793
+
794
+ return .{ .row = 0, .col = 0, .desired_col = 0, .offset = 0 };
795
+ }
796
+
797
+ pub fn getEOL(self: *EditBuffer) Cursor {
798
+ if (self.cursors.items.len == 0) return .{ .row = 0, .col = 0 };
799
+ const cursor = self.cursors.items[0];
800
+
801
+ const line_count = self.tb.lineCount();
802
+ if (cursor.row >= line_count) return cursor;
803
+
804
+ const line_width = iter_mod.lineWidthAt(self.tb.rope(), cursor.row);
805
+ const offset = iter_mod.coordsToOffset(self.tb.rope(), cursor.row, line_width) orelse cursor.offset;
806
+
807
+ return .{ .row = cursor.row, .col = line_width, .desired_col = line_width, .offset = offset };
808
+ }
809
+
810
+ /// Get text within a range of display-width offsets
811
+ /// Automatically snaps to grapheme boundaries:
812
+ /// - start_offset excludes graphemes that start before it
813
+ /// - end_offset includes graphemes that start before it
814
+ /// Returns number of bytes written to out_buffer
815
+ pub fn getTextRange(self: *EditBuffer, start_offset: u32, end_offset: u32, out_buffer: []u8) !usize {
816
+ return self.tb.getTextRange(start_offset, end_offset, out_buffer);
817
+ }
818
+
819
+ /// Get text within a range specified by row/col coordinates
820
+ /// Automatically snaps to grapheme boundaries:
821
+ /// Returns number of bytes written to out_buffer
822
+ pub fn getTextRangeByCoords(self: *EditBuffer, start_row: u32, start_col: u32, end_row: u32, end_col: u32, out_buffer: []u8) usize {
823
+ return self.tb.getTextRangeByCoords(start_row, start_col, end_row, end_col, out_buffer);
824
+ }
825
+ };