@react-x11/components 0.1.0 → 0.2.0

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 (783) hide show
  1. package/README.md +979 -21
  2. package/dist/ansi/cast.d.ts +48 -0
  3. package/dist/ansi/cast.d.ts.map +1 -0
  4. package/dist/ansi/cast.js +135 -0
  5. package/dist/ansi/cast.js.map +1 -0
  6. package/dist/ansi/index.d.ts +10 -0
  7. package/dist/ansi/index.d.ts.map +1 -0
  8. package/dist/ansi/index.js +27 -0
  9. package/dist/ansi/index.js.map +1 -0
  10. package/dist/ansi/palette.d.ts +79 -0
  11. package/dist/ansi/palette.d.ts.map +1 -0
  12. package/dist/ansi/palette.js +155 -0
  13. package/dist/ansi/palette.js.map +1 -0
  14. package/dist/ansi/parse.d.ts +114 -0
  15. package/dist/ansi/parse.d.ts.map +1 -0
  16. package/dist/ansi/parse.js +687 -0
  17. package/dist/ansi/parse.js.map +1 -0
  18. package/dist/ansi/sgr.d.ts +87 -0
  19. package/dist/ansi/sgr.d.ts.map +1 -0
  20. package/dist/ansi/sgr.js +302 -0
  21. package/dist/ansi/sgr.js.map +1 -0
  22. package/dist/ansi/utf8.d.ts +15 -0
  23. package/dist/ansi/utf8.d.ts.map +1 -0
  24. package/dist/ansi/utf8.js +82 -0
  25. package/dist/ansi/utf8.js.map +1 -0
  26. package/dist/calendar/Calendar.d.ts +136 -0
  27. package/dist/calendar/Calendar.d.ts.map +1 -0
  28. package/dist/calendar/Calendar.js +460 -0
  29. package/dist/calendar/Calendar.js.map +1 -0
  30. package/dist/calendar/DatePicker.d.ts +52 -0
  31. package/dist/calendar/DatePicker.d.ts.map +1 -0
  32. package/dist/calendar/DatePicker.js +257 -0
  33. package/dist/calendar/DatePicker.js.map +1 -0
  34. package/dist/calendar/dates.d.ts +90 -0
  35. package/dist/calendar/dates.d.ts.map +1 -0
  36. package/dist/calendar/dates.js +236 -0
  37. package/dist/calendar/dates.js.map +1 -0
  38. package/dist/calendar/index.d.ts +8 -0
  39. package/dist/calendar/index.d.ts.map +1 -0
  40. package/dist/calendar/index.js +15 -0
  41. package/dist/calendar/index.js.map +1 -0
  42. package/dist/charts/components.d.ts +141 -0
  43. package/dist/charts/components.d.ts.map +1 -0
  44. package/dist/charts/components.js +580 -0
  45. package/dist/charts/components.js.map +1 -0
  46. package/dist/charts/data.d.ts +198 -0
  47. package/dist/charts/data.d.ts.map +1 -0
  48. package/dist/charts/data.js +616 -0
  49. package/dist/charts/data.js.map +1 -0
  50. package/dist/charts/hx.d.ts +21 -0
  51. package/dist/charts/hx.d.ts.map +1 -0
  52. package/dist/charts/hx.js +15 -0
  53. package/dist/charts/hx.js.map +1 -0
  54. package/dist/charts/index.d.ts +27 -0
  55. package/dist/charts/index.d.ts.map +1 -0
  56. package/dist/charts/index.js +38 -0
  57. package/dist/charts/index.js.map +1 -0
  58. package/dist/charts/node.d.ts +96 -0
  59. package/dist/charts/node.d.ts.map +1 -0
  60. package/dist/charts/node.js +720 -0
  61. package/dist/charts/node.js.map +1 -0
  62. package/dist/charts/render.d.ts +153 -0
  63. package/dist/charts/render.d.ts.map +1 -0
  64. package/dist/charts/render.js +762 -0
  65. package/dist/charts/render.js.map +1 -0
  66. package/dist/charts/scale.d.ts +42 -0
  67. package/dist/charts/scale.d.ts.map +1 -0
  68. package/dist/charts/scale.js +173 -0
  69. package/dist/charts/scale.js.map +1 -0
  70. package/dist/charts/spec.d.ts +91 -0
  71. package/dist/charts/spec.d.ts.map +1 -0
  72. package/dist/charts/spec.js +33 -0
  73. package/dist/charts/spec.js.map +1 -0
  74. package/dist/charts/timers.d.ts +6 -0
  75. package/dist/charts/timers.d.ts.map +1 -0
  76. package/dist/charts/timers.js +14 -0
  77. package/dist/charts/timers.js.map +1 -0
  78. package/dist/code/hx.d.ts +24 -0
  79. package/dist/code/hx.d.ts.map +1 -0
  80. package/dist/code/hx.js +25 -0
  81. package/dist/code/hx.js.map +1 -0
  82. package/dist/code/index.d.ts +45 -0
  83. package/dist/code/index.d.ts.map +1 -0
  84. package/dist/code/index.js +93 -0
  85. package/dist/code/index.js.map +1 -0
  86. package/dist/code-editor/completion.d.ts +26 -0
  87. package/dist/code-editor/completion.d.ts.map +1 -0
  88. package/dist/code-editor/completion.js +129 -0
  89. package/dist/code-editor/completion.js.map +1 -0
  90. package/dist/code-editor/doc.d.ts +35 -0
  91. package/dist/code-editor/doc.d.ts.map +1 -0
  92. package/dist/code-editor/doc.js +162 -0
  93. package/dist/code-editor/doc.js.map +1 -0
  94. package/dist/code-editor/hx.d.ts +13 -0
  95. package/dist/code-editor/hx.d.ts.map +1 -0
  96. package/dist/code-editor/hx.js +10 -0
  97. package/dist/code-editor/hx.js.map +1 -0
  98. package/dist/code-editor/index.d.ts +88 -0
  99. package/dist/code-editor/index.d.ts.map +1 -0
  100. package/dist/code-editor/index.js +401 -0
  101. package/dist/code-editor/index.js.map +1 -0
  102. package/dist/code-editor/node.d.ts +343 -0
  103. package/dist/code-editor/node.d.ts.map +1 -0
  104. package/dist/code-editor/node.js +1521 -0
  105. package/dist/code-editor/node.js.map +1 -0
  106. package/dist/code-language/hljs.d.ts +47 -0
  107. package/dist/code-language/hljs.d.ts.map +1 -0
  108. package/dist/code-language/hljs.js +211 -0
  109. package/dist/code-language/hljs.js.map +1 -0
  110. package/dist/code-language/index.d.ts +22 -0
  111. package/dist/code-language/index.d.ts.map +1 -0
  112. package/dist/code-language/index.js +20 -0
  113. package/dist/code-language/index.js.map +1 -0
  114. package/dist/code-language/languages/glsl.d.ts +4 -0
  115. package/dist/code-language/languages/glsl.d.ts.map +1 -0
  116. package/dist/code-language/languages/glsl.js +99 -0
  117. package/dist/code-language/languages/glsl.js.map +1 -0
  118. package/dist/code-language/languages/javascript.d.ts +10 -0
  119. package/dist/code-language/languages/javascript.d.ts.map +1 -0
  120. package/dist/code-language/languages/javascript.js +360 -0
  121. package/dist/code-language/languages/javascript.js.map +1 -0
  122. package/dist/code-language/languages/json.d.ts +4 -0
  123. package/dist/code-language/languages/json.d.ts.map +1 -0
  124. package/dist/code-language/languages/json.js +68 -0
  125. package/dist/code-language/languages/json.js.map +1 -0
  126. package/dist/code-language/languages/shell.d.ts +4 -0
  127. package/dist/code-language/languages/shell.d.ts.map +1 -0
  128. package/dist/code-language/languages/shell.js +189 -0
  129. package/dist/code-language/languages/shell.js.map +1 -0
  130. package/dist/code-language/languages/sql.d.ts +11 -0
  131. package/dist/code-language/languages/sql.d.ts.map +1 -0
  132. package/dist/code-language/languages/sql.js +133 -0
  133. package/dist/code-language/languages/sql.js.map +1 -0
  134. package/dist/code-language/lezer.d.ts +24 -0
  135. package/dist/code-language/lezer.d.ts.map +1 -0
  136. package/dist/code-language/lezer.js +148 -0
  137. package/dist/code-language/lezer.js.map +1 -0
  138. package/dist/code-language/registry.d.ts +19 -0
  139. package/dist/code-language/registry.d.ts.map +1 -0
  140. package/dist/code-language/registry.js +56 -0
  141. package/dist/code-language/registry.js.map +1 -0
  142. package/dist/code-language/runs.d.ts +35 -0
  143. package/dist/code-language/runs.d.ts.map +1 -0
  144. package/dist/code-language/runs.js +88 -0
  145. package/dist/code-language/runs.js.map +1 -0
  146. package/dist/code-language/stream.d.ts +70 -0
  147. package/dist/code-language/stream.d.ts.map +1 -0
  148. package/dist/code-language/stream.js +243 -0
  149. package/dist/code-language/stream.js.map +1 -0
  150. package/dist/code-language/textmate.d.ts +27 -0
  151. package/dist/code-language/textmate.d.ts.map +1 -0
  152. package/dist/code-language/textmate.js +86 -0
  153. package/dist/code-language/textmate.js.map +1 -0
  154. package/dist/code-language/theme.d.ts +26 -0
  155. package/dist/code-language/theme.d.ts.map +1 -0
  156. package/dist/code-language/theme.js +108 -0
  157. package/dist/code-language/theme.js.map +1 -0
  158. package/dist/code-language/timers.d.ts +7 -0
  159. package/dist/code-language/timers.d.ts.map +1 -0
  160. package/dist/code-language/timers.js +22 -0
  161. package/dist/code-language/timers.js.map +1 -0
  162. package/dist/code-language/types.d.ts +173 -0
  163. package/dist/code-language/types.d.ts.map +1 -0
  164. package/dist/code-language/types.js +41 -0
  165. package/dist/code-language/types.js.map +1 -0
  166. package/dist/codeblock/index.d.ts +69 -0
  167. package/dist/codeblock/index.d.ts.map +1 -0
  168. package/dist/codeblock/index.js +85 -0
  169. package/dist/codeblock/index.js.map +1 -0
  170. package/dist/color-picker/ColorField.d.ts +39 -0
  171. package/dist/color-picker/ColorField.d.ts.map +1 -0
  172. package/dist/color-picker/ColorField.js +239 -0
  173. package/dist/color-picker/ColorField.js.map +1 -0
  174. package/dist/color-picker/ColorPicker.d.ts +109 -0
  175. package/dist/color-picker/ColorPicker.d.ts.map +1 -0
  176. package/dist/color-picker/ColorPicker.js +757 -0
  177. package/dist/color-picker/ColorPicker.js.map +1 -0
  178. package/dist/color-picker/color.d.ts +105 -0
  179. package/dist/color-picker/color.d.ts.map +1 -0
  180. package/dist/color-picker/color.js +313 -0
  181. package/dist/color-picker/color.js.map +1 -0
  182. package/dist/color-picker/index.d.ts +8 -0
  183. package/dist/color-picker/index.d.ts.map +1 -0
  184. package/dist/color-picker/index.js +19 -0
  185. package/dist/color-picker/index.js.map +1 -0
  186. package/dist/desktop-calendar/eds.d.ts +102 -0
  187. package/dist/desktop-calendar/eds.d.ts.map +1 -0
  188. package/dist/desktop-calendar/eds.js +356 -0
  189. package/dist/desktop-calendar/eds.js.map +1 -0
  190. package/dist/desktop-calendar/ical.d.ts +63 -0
  191. package/dist/desktop-calendar/ical.d.ts.map +1 -0
  192. package/dist/desktop-calendar/ical.js +57 -0
  193. package/dist/desktop-calendar/ical.js.map +1 -0
  194. package/dist/desktop-calendar/index.d.ts +72 -0
  195. package/dist/desktop-calendar/index.d.ts.map +1 -0
  196. package/dist/desktop-calendar/index.js +213 -0
  197. package/dist/desktop-calendar/index.js.map +1 -0
  198. package/dist/embed/client.d.ts +121 -0
  199. package/dist/embed/client.d.ts.map +1 -0
  200. package/dist/embed/client.js +203 -0
  201. package/dist/embed/client.js.map +1 -0
  202. package/dist/embed/host.d.ts +96 -0
  203. package/dist/embed/host.d.ts.map +1 -0
  204. package/dist/embed/host.js +266 -0
  205. package/dist/embed/host.js.map +1 -0
  206. package/dist/embed/index.d.ts +5 -0
  207. package/dist/embed/index.d.ts.map +1 -0
  208. package/dist/embed/index.js +13 -0
  209. package/dist/embed/index.js.map +1 -0
  210. package/dist/embed/timers.d.ts +6 -0
  211. package/dist/embed/timers.d.ts.map +1 -0
  212. package/dist/embed/timers.js +16 -0
  213. package/dist/embed/timers.js.map +1 -0
  214. package/dist/flow/draw.d.ts +45 -0
  215. package/dist/flow/draw.d.ts.map +1 -0
  216. package/dist/flow/draw.js +295 -0
  217. package/dist/flow/draw.js.map +1 -0
  218. package/dist/flow/index.d.ts +64 -0
  219. package/dist/flow/index.d.ts.map +1 -0
  220. package/dist/flow/index.js +306 -0
  221. package/dist/flow/index.js.map +1 -0
  222. package/dist/flow/model.d.ts +158 -0
  223. package/dist/flow/model.d.ts.map +1 -0
  224. package/dist/flow/model.js +566 -0
  225. package/dist/flow/model.js.map +1 -0
  226. package/dist/flow/node.d.ts +429 -0
  227. package/dist/flow/node.d.ts.map +1 -0
  228. package/dist/flow/node.js +3025 -0
  229. package/dist/flow/node.js.map +1 -0
  230. package/dist/flow/paths.d.ts +43 -0
  231. package/dist/flow/paths.d.ts.map +1 -0
  232. package/dist/flow/paths.js +327 -0
  233. package/dist/flow/paths.js.map +1 -0
  234. package/dist/flow/types.d.ts +561 -0
  235. package/dist/flow/types.d.ts.map +1 -0
  236. package/dist/flow/types.js +2 -0
  237. package/dist/flow/types.js.map +1 -0
  238. package/dist/formula/hx.d.ts +24 -0
  239. package/dist/formula/hx.d.ts.map +1 -0
  240. package/dist/formula/hx.js +25 -0
  241. package/dist/formula/hx.js.map +1 -0
  242. package/dist/formula/index.d.ts +74 -0
  243. package/dist/formula/index.d.ts.map +1 -0
  244. package/dist/formula/index.js +155 -0
  245. package/dist/formula/index.js.map +1 -0
  246. package/dist/formula/katex.d.ts +78 -0
  247. package/dist/formula/katex.d.ts.map +1 -0
  248. package/dist/formula/katex.js +130 -0
  249. package/dist/formula/katex.js.map +1 -0
  250. package/dist/formula/layout.d.ts +103 -0
  251. package/dist/formula/layout.d.ts.map +1 -0
  252. package/dist/formula/layout.js +716 -0
  253. package/dist/formula/layout.js.map +1 -0
  254. package/dist/formula/node.d.ts +50 -0
  255. package/dist/formula/node.d.ts.map +1 -0
  256. package/dist/formula/node.js +351 -0
  257. package/dist/formula/node.js.map +1 -0
  258. package/dist/html/controls.d.ts +48 -0
  259. package/dist/html/controls.d.ts.map +1 -0
  260. package/dist/html/controls.js +195 -0
  261. package/dist/html/controls.js.map +1 -0
  262. package/dist/html/css/cascade.d.ts +51 -0
  263. package/dist/html/css/cascade.d.ts.map +1 -0
  264. package/dist/html/css/cascade.js +552 -0
  265. package/dist/html/css/cascade.js.map +1 -0
  266. package/dist/html/css/parse.d.ts +74 -0
  267. package/dist/html/css/parse.d.ts.map +1 -0
  268. package/dist/html/css/parse.js +464 -0
  269. package/dist/html/css/parse.js.map +1 -0
  270. package/dist/html/css/style.d.ts +142 -0
  271. package/dist/html/css/style.d.ts.map +1 -0
  272. package/dist/html/css/style.js +1145 -0
  273. package/dist/html/css/style.js.map +1 -0
  274. package/dist/html/css/ua.d.ts +5 -0
  275. package/dist/html/css/ua.d.ts.map +1 -0
  276. package/dist/html/css/ua.js +171 -0
  277. package/dist/html/css/ua.js.map +1 -0
  278. package/dist/html/css/values.d.ts +78 -0
  279. package/dist/html/css/values.d.ts.map +1 -0
  280. package/dist/html/css/values.js +330 -0
  281. package/dist/html/css/values.js.map +1 -0
  282. package/dist/html/dom.d.ts +112 -0
  283. package/dist/html/dom.d.ts.map +1 -0
  284. package/dist/html/dom.js +374 -0
  285. package/dist/html/dom.js.map +1 -0
  286. package/dist/html/hx.d.ts +24 -0
  287. package/dist/html/hx.d.ts.map +1 -0
  288. package/dist/html/hx.js +29 -0
  289. package/dist/html/hx.js.map +1 -0
  290. package/dist/html/index.d.ts +137 -0
  291. package/dist/html/index.d.ts.map +1 -0
  292. package/dist/html/index.js +339 -0
  293. package/dist/html/index.js.map +1 -0
  294. package/dist/html/layout/block.d.ts +58 -0
  295. package/dist/html/layout/block.d.ts.map +1 -0
  296. package/dist/html/layout/block.js +724 -0
  297. package/dist/html/layout/block.js.map +1 -0
  298. package/dist/html/layout/boxes.d.ts +261 -0
  299. package/dist/html/layout/boxes.d.ts.map +1 -0
  300. package/dist/html/layout/boxes.js +725 -0
  301. package/dist/html/layout/boxes.js.map +1 -0
  302. package/dist/html/layout/flex.d.ts +16 -0
  303. package/dist/html/layout/flex.d.ts.map +1 -0
  304. package/dist/html/layout/flex.js +242 -0
  305. package/dist/html/layout/flex.js.map +1 -0
  306. package/dist/html/layout/floats.d.ts +50 -0
  307. package/dist/html/layout/floats.d.ts.map +1 -0
  308. package/dist/html/layout/floats.js +110 -0
  309. package/dist/html/layout/floats.js.map +1 -0
  310. package/dist/html/layout/inline.d.ts +44 -0
  311. package/dist/html/layout/inline.d.ts.map +1 -0
  312. package/dist/html/layout/inline.js +700 -0
  313. package/dist/html/layout/inline.js.map +1 -0
  314. package/dist/html/layout/table.d.ts +5 -0
  315. package/dist/html/layout/table.d.ts.map +1 -0
  316. package/dist/html/layout/table.js +340 -0
  317. package/dist/html/layout/table.js.map +1 -0
  318. package/dist/html/node.d.ts +149 -0
  319. package/dist/html/node.d.ts.map +1 -0
  320. package/dist/html/node.js +861 -0
  321. package/dist/html/node.js.map +1 -0
  322. package/dist/html/paint.d.ts +48 -0
  323. package/dist/html/paint.d.ts.map +1 -0
  324. package/dist/html/paint.js +495 -0
  325. package/dist/html/paint.js.map +1 -0
  326. package/dist/html/resources.d.ts +48 -0
  327. package/dist/html/resources.d.ts.map +1 -0
  328. package/dist/html/resources.js +156 -0
  329. package/dist/html/resources.js.map +1 -0
  330. package/dist/index.d.ts +48 -0
  331. package/dist/index.d.ts.map +1 -0
  332. package/dist/index.js +92 -0
  333. package/dist/index.js.map +1 -0
  334. package/dist/internal/heights.d.ts +81 -0
  335. package/dist/internal/heights.d.ts.map +1 -0
  336. package/dist/internal/heights.js +193 -0
  337. package/dist/internal/heights.js.map +1 -0
  338. package/dist/internal/hx.d.ts +24 -0
  339. package/dist/internal/hx.d.ts.map +1 -0
  340. package/dist/internal/hx.js +27 -0
  341. package/dist/internal/hx.js.map +1 -0
  342. package/dist/internal/text.d.ts +6 -0
  343. package/dist/internal/text.d.ts.map +1 -0
  344. package/dist/internal/text.js +44 -0
  345. package/dist/internal/text.js.map +1 -0
  346. package/dist/internal/timers.d.ts +8 -0
  347. package/dist/internal/timers.d.ts.map +1 -0
  348. package/dist/internal/timers.js +22 -0
  349. package/dist/internal/timers.js.map +1 -0
  350. package/dist/internal/widget.d.ts +40 -0
  351. package/dist/internal/widget.d.ts.map +1 -0
  352. package/dist/internal/widget.js +44 -0
  353. package/dist/internal/widget.js.map +1 -0
  354. package/dist/markdown/ast.d.ts +126 -0
  355. package/dist/markdown/ast.d.ts.map +1 -0
  356. package/dist/markdown/ast.js +8 -0
  357. package/dist/markdown/ast.js.map +1 -0
  358. package/dist/markdown/hx.d.ts +24 -0
  359. package/dist/markdown/hx.d.ts.map +1 -0
  360. package/dist/markdown/hx.js +25 -0
  361. package/dist/markdown/hx.js.map +1 -0
  362. package/dist/markdown/index.d.ts +90 -0
  363. package/dist/markdown/index.d.ts.map +1 -0
  364. package/dist/markdown/index.js +413 -0
  365. package/dist/markdown/index.js.map +1 -0
  366. package/dist/markdown/parse.d.ts +8 -0
  367. package/dist/markdown/parse.d.ts.map +1 -0
  368. package/dist/markdown/parse.js +911 -0
  369. package/dist/markdown/parse.js.map +1 -0
  370. package/dist/markdown/spans.d.ts +20 -0
  371. package/dist/markdown/spans.d.ts.map +1 -0
  372. package/dist/markdown/spans.js +96 -0
  373. package/dist/markdown/spans.js.map +1 -0
  374. package/dist/media-player/backends.d.ts +64 -0
  375. package/dist/media-player/backends.d.ts.map +1 -0
  376. package/dist/media-player/backends.js +110 -0
  377. package/dist/media-player/backends.js.map +1 -0
  378. package/dist/media-player/control.d.ts +40 -0
  379. package/dist/media-player/control.d.ts.map +1 -0
  380. package/dist/media-player/control.js +44 -0
  381. package/dist/media-player/control.js.map +1 -0
  382. package/dist/media-player/hx.d.ts +24 -0
  383. package/dist/media-player/hx.d.ts.map +1 -0
  384. package/dist/media-player/hx.js +25 -0
  385. package/dist/media-player/hx.js.map +1 -0
  386. package/dist/media-player/index.d.ts +107 -0
  387. package/dist/media-player/index.d.ts.map +1 -0
  388. package/dist/media-player/index.js +219 -0
  389. package/dist/media-player/index.js.map +1 -0
  390. package/dist/media-player/mpv.d.ts +4 -0
  391. package/dist/media-player/mpv.d.ts.map +1 -0
  392. package/dist/media-player/mpv.js +120 -0
  393. package/dist/media-player/mpv.js.map +1 -0
  394. package/dist/media-player/vlc.d.ts +8 -0
  395. package/dist/media-player/vlc.d.ts.map +1 -0
  396. package/dist/media-player/vlc.js +83 -0
  397. package/dist/media-player/vlc.js.map +1 -0
  398. package/dist/richtext/index.d.ts +19 -0
  399. package/dist/richtext/index.d.ts.map +1 -0
  400. package/dist/richtext/index.js +4 -0
  401. package/dist/richtext/index.js.map +1 -0
  402. package/dist/richtext/links.d.ts +12 -0
  403. package/dist/richtext/links.d.ts.map +1 -0
  404. package/dist/richtext/links.js +64 -0
  405. package/dist/richtext/links.js.map +1 -0
  406. package/dist/richtext/menu.d.ts +11 -0
  407. package/dist/richtext/menu.d.ts.map +1 -0
  408. package/dist/richtext/menu.js +43 -0
  409. package/dist/richtext/menu.js.map +1 -0
  410. package/dist/richtext/node.d.ts +151 -0
  411. package/dist/richtext/node.d.ts.map +1 -0
  412. package/dist/richtext/node.js +255 -0
  413. package/dist/richtext/node.js.map +1 -0
  414. package/dist/richtext/runs.d.ts +92 -0
  415. package/dist/richtext/runs.d.ts.map +1 -0
  416. package/dist/richtext/runs.js +158 -0
  417. package/dist/richtext/runs.js.map +1 -0
  418. package/dist/table/hx.d.ts +24 -0
  419. package/dist/table/hx.d.ts.map +1 -0
  420. package/dist/table/hx.js +25 -0
  421. package/dist/table/hx.js.map +1 -0
  422. package/dist/table/index.d.ts +169 -0
  423. package/dist/table/index.d.ts.map +1 -0
  424. package/dist/table/index.js +844 -0
  425. package/dist/table/index.js.map +1 -0
  426. package/dist/table/rows.d.ts +136 -0
  427. package/dist/table/rows.d.ts.map +1 -0
  428. package/dist/table/rows.js +146 -0
  429. package/dist/table/rows.js.map +1 -0
  430. package/dist/terminal/backends.d.ts +105 -0
  431. package/dist/terminal/backends.d.ts.map +1 -0
  432. package/dist/terminal/backends.js +185 -0
  433. package/dist/terminal/backends.js.map +1 -0
  434. package/dist/terminal/hx.d.ts +24 -0
  435. package/dist/terminal/hx.d.ts.map +1 -0
  436. package/dist/terminal/hx.js +25 -0
  437. package/dist/terminal/hx.js.map +1 -0
  438. package/dist/terminal/index.d.ts +160 -0
  439. package/dist/terminal/index.d.ts.map +1 -0
  440. package/dist/terminal/index.js +344 -0
  441. package/dist/terminal/index.js.map +1 -0
  442. package/dist/terminal/title.d.ts +24 -0
  443. package/dist/terminal/title.d.ts.map +1 -0
  444. package/dist/terminal/title.js +129 -0
  445. package/dist/terminal/title.js.map +1 -0
  446. package/dist/terminal/vt/colors.d.ts +63 -0
  447. package/dist/terminal/vt/colors.d.ts.map +1 -0
  448. package/dist/terminal/vt/colors.js +203 -0
  449. package/dist/terminal/vt/colors.js.map +1 -0
  450. package/dist/terminal/vt/diff.d.ts +117 -0
  451. package/dist/terminal/vt/diff.d.ts.map +1 -0
  452. package/dist/terminal/vt/diff.js +358 -0
  453. package/dist/terminal/vt/diff.js.map +1 -0
  454. package/dist/terminal/vt/fonts.d.ts +92 -0
  455. package/dist/terminal/vt/fonts.d.ts.map +1 -0
  456. package/dist/terminal/vt/fonts.js +178 -0
  457. package/dist/terminal/vt/fonts.js.map +1 -0
  458. package/dist/terminal/vt/index.d.ts +78 -0
  459. package/dist/terminal/vt/index.d.ts.map +1 -0
  460. package/dist/terminal/vt/index.js +312 -0
  461. package/dist/terminal/vt/index.js.map +1 -0
  462. package/dist/terminal/vt/keys.d.ts +74 -0
  463. package/dist/terminal/vt/keys.d.ts.map +1 -0
  464. package/dist/terminal/vt/keys.js +309 -0
  465. package/dist/terminal/vt/keys.js.map +1 -0
  466. package/dist/terminal/vt/mouse.d.ts +44 -0
  467. package/dist/terminal/vt/mouse.d.ts.map +1 -0
  468. package/dist/terminal/vt/mouse.js +106 -0
  469. package/dist/terminal/vt/mouse.js.map +1 -0
  470. package/dist/terminal/vt/node.d.ts +246 -0
  471. package/dist/terminal/vt/node.d.ts.map +1 -0
  472. package/dist/terminal/vt/node.js +1068 -0
  473. package/dist/terminal/vt/node.js.map +1 -0
  474. package/dist/terminal/vt/pty.d.ts +81 -0
  475. package/dist/terminal/vt/pty.d.ts.map +1 -0
  476. package/dist/terminal/vt/pty.js +201 -0
  477. package/dist/terminal/vt/pty.js.map +1 -0
  478. package/dist/terminal/vt/renderer.d.ts +179 -0
  479. package/dist/terminal/vt/renderer.d.ts.map +1 -0
  480. package/dist/terminal/vt/renderer.js +357 -0
  481. package/dist/terminal/vt/renderer.js.map +1 -0
  482. package/dist/terminal/vt/timers.d.ts +6 -0
  483. package/dist/terminal/vt/timers.d.ts.map +1 -0
  484. package/dist/terminal/vt/timers.js +16 -0
  485. package/dist/terminal/vt/timers.js.map +1 -0
  486. package/dist/terminal/vt/xterm.d.ts +142 -0
  487. package/dist/terminal/vt/xterm.d.ts.map +1 -0
  488. package/dist/terminal/vt/xterm.js +32 -0
  489. package/dist/terminal/vt/xterm.js.map +1 -0
  490. package/dist/terminal-output/hx.d.ts +24 -0
  491. package/dist/terminal-output/hx.d.ts.map +1 -0
  492. package/dist/terminal-output/hx.js +25 -0
  493. package/dist/terminal-output/hx.js.map +1 -0
  494. package/dist/terminal-output/index.d.ts +92 -0
  495. package/dist/terminal-output/index.d.ts.map +1 -0
  496. package/dist/terminal-output/index.js +179 -0
  497. package/dist/terminal-output/index.js.map +1 -0
  498. package/dist/terminal-output/runs.d.ts +27 -0
  499. package/dist/terminal-output/runs.d.ts.map +1 -0
  500. package/dist/terminal-output/runs.js +66 -0
  501. package/dist/terminal-output/runs.js.map +1 -0
  502. package/dist/three/canvas.d.ts +46 -0
  503. package/dist/three/canvas.d.ts.map +1 -0
  504. package/dist/three/canvas.js +305 -0
  505. package/dist/three/canvas.js.map +1 -0
  506. package/dist/three/events.d.ts +64 -0
  507. package/dist/three/events.d.ts.map +1 -0
  508. package/dist/three/events.js +146 -0
  509. package/dist/three/events.js.map +1 -0
  510. package/dist/three/geometries.d.ts +89 -0
  511. package/dist/three/geometries.d.ts.map +1 -0
  512. package/dist/three/geometries.js +345 -0
  513. package/dist/three/geometries.js.map +1 -0
  514. package/dist/three/globals.d.ts +7 -0
  515. package/dist/three/globals.d.ts.map +1 -0
  516. package/dist/three/globals.js +20 -0
  517. package/dist/three/globals.js.map +1 -0
  518. package/dist/three/hx.d.ts +9 -0
  519. package/dist/three/hx.d.ts.map +1 -0
  520. package/dist/three/hx.js +10 -0
  521. package/dist/three/hx.js.map +1 -0
  522. package/dist/three/index.d.ts +22 -0
  523. package/dist/three/index.d.ts.map +1 -0
  524. package/dist/three/index.js +24 -0
  525. package/dist/three/index.js.map +1 -0
  526. package/dist/three/jsx-dev-runtime.d.ts +19 -0
  527. package/dist/three/jsx-dev-runtime.d.ts.map +1 -0
  528. package/dist/three/jsx-dev-runtime.js +2 -0
  529. package/dist/three/jsx-dev-runtime.js.map +1 -0
  530. package/dist/three/jsx-runtime.d.ts +19 -0
  531. package/dist/three/jsx-runtime.d.ts.map +1 -0
  532. package/dist/three/jsx-runtime.js +2 -0
  533. package/dist/three/jsx-runtime.js.map +1 -0
  534. package/dist/three/jsx.d.ts +198 -0
  535. package/dist/three/jsx.d.ts.map +1 -0
  536. package/dist/three/jsx.js +2 -0
  537. package/dist/three/jsx.js.map +1 -0
  538. package/dist/three/materials.d.ts +125 -0
  539. package/dist/three/materials.d.ts.map +1 -0
  540. package/dist/three/materials.js +229 -0
  541. package/dist/three/materials.js.map +1 -0
  542. package/dist/three/math.d.ts +146 -0
  543. package/dist/three/math.d.ts.map +1 -0
  544. package/dist/three/math.js +464 -0
  545. package/dist/three/math.js.map +1 -0
  546. package/dist/three/objects.d.ts +185 -0
  547. package/dist/three/objects.d.ts.map +1 -0
  548. package/dist/three/objects.js +309 -0
  549. package/dist/three/objects.js.map +1 -0
  550. package/dist/three/passes.d.ts +54 -0
  551. package/dist/three/passes.d.ts.map +1 -0
  552. package/dist/three/passes.js +78 -0
  553. package/dist/three/passes.js.map +1 -0
  554. package/dist/three/postprocess.d.ts +85 -0
  555. package/dist/three/postprocess.d.ts.map +1 -0
  556. package/dist/three/postprocess.js +503 -0
  557. package/dist/three/postprocess.js.map +1 -0
  558. package/dist/three/raycast.d.ts +24 -0
  559. package/dist/three/raycast.d.ts.map +1 -0
  560. package/dist/three/raycast.js +138 -0
  561. package/dist/three/raycast.js.map +1 -0
  562. package/dist/three/reconciler.d.ts +48 -0
  563. package/dist/three/reconciler.d.ts.map +1 -0
  564. package/dist/three/reconciler.js +416 -0
  565. package/dist/three/reconciler.js.map +1 -0
  566. package/dist/three/renderer-direct.d.ts +102 -0
  567. package/dist/three/renderer-direct.d.ts.map +1 -0
  568. package/dist/three/renderer-direct.js +782 -0
  569. package/dist/three/renderer-direct.js.map +1 -0
  570. package/dist/three/renderer-indirect.d.ts +68 -0
  571. package/dist/three/renderer-indirect.d.ts.map +1 -0
  572. package/dist/three/renderer-indirect.js +438 -0
  573. package/dist/three/renderer-indirect.js.map +1 -0
  574. package/dist/three/store.d.ts +81 -0
  575. package/dist/three/store.d.ts.map +1 -0
  576. package/dist/three/store.js +110 -0
  577. package/dist/three/store.js.map +1 -0
  578. package/dist/timeline/hx.d.ts +18 -0
  579. package/dist/timeline/hx.d.ts.map +1 -0
  580. package/dist/timeline/hx.js +10 -0
  581. package/dist/timeline/hx.js.map +1 -0
  582. package/dist/timeline/index.d.ts +152 -0
  583. package/dist/timeline/index.d.ts.map +1 -0
  584. package/dist/timeline/index.js +383 -0
  585. package/dist/timeline/index.js.map +1 -0
  586. package/dist/tray-host/hx.d.ts +24 -0
  587. package/dist/tray-host/hx.d.ts.map +1 -0
  588. package/dist/tray-host/hx.js +25 -0
  589. package/dist/tray-host/hx.js.map +1 -0
  590. package/dist/tray-host/index.d.ts +110 -0
  591. package/dist/tray-host/index.d.ts.map +1 -0
  592. package/dist/tray-host/index.js +191 -0
  593. package/dist/tray-host/index.js.map +1 -0
  594. package/dist/tray-host/manager.d.ts +137 -0
  595. package/dist/tray-host/manager.d.ts.map +1 -0
  596. package/dist/tray-host/manager.js +462 -0
  597. package/dist/tray-host/manager.js.map +1 -0
  598. package/dist/tray-host/notify.d.ts +22 -0
  599. package/dist/tray-host/notify.d.ts.map +1 -0
  600. package/dist/tray-host/notify.js +74 -0
  601. package/dist/tray-host/notify.js.map +1 -0
  602. package/dist/tray-host/protocol.d.ts +106 -0
  603. package/dist/tray-host/protocol.d.ts.map +1 -0
  604. package/dist/tray-host/protocol.js +191 -0
  605. package/dist/tray-host/protocol.js.map +1 -0
  606. package/dist/tree/hx.d.ts +24 -0
  607. package/dist/tree/hx.d.ts.map +1 -0
  608. package/dist/tree/hx.js +25 -0
  609. package/dist/tree/hx.js.map +1 -0
  610. package/dist/tree/index.d.ts +221 -0
  611. package/dist/tree/index.d.ts.map +1 -0
  612. package/dist/tree/index.js +716 -0
  613. package/dist/tree/index.js.map +1 -0
  614. package/dist/tree/internal.d.ts +18 -0
  615. package/dist/tree/internal.d.ts.map +1 -0
  616. package/dist/tree/internal.js +66 -0
  617. package/dist/tree/internal.js.map +1 -0
  618. package/dist/tree/rows.d.ts +177 -0
  619. package/dist/tree/rows.d.ts.map +1 -0
  620. package/dist/tree/rows.js +179 -0
  621. package/dist/tree/rows.js.map +1 -0
  622. package/package.json +166 -12
  623. package/src/ansi/cast.ts +159 -0
  624. package/src/ansi/index.ts +53 -0
  625. package/src/ansi/palette.ts +226 -0
  626. package/src/ansi/parse.ts +780 -0
  627. package/src/ansi/sgr.ts +380 -0
  628. package/src/ansi/utf8.ts +84 -0
  629. package/src/calendar/Calendar.ts +744 -0
  630. package/src/calendar/DatePicker.ts +368 -0
  631. package/src/calendar/dates.ts +307 -0
  632. package/src/calendar/index.ts +51 -0
  633. package/src/charts/components.ts +864 -0
  634. package/src/charts/data.ts +776 -0
  635. package/src/charts/hx.ts +39 -0
  636. package/src/charts/index.ts +99 -0
  637. package/src/charts/node.ts +932 -0
  638. package/src/charts/render.ts +982 -0
  639. package/src/charts/scale.ts +214 -0
  640. package/src/charts/spec.ts +122 -0
  641. package/src/charts/timers.ts +24 -0
  642. package/src/code/hx.ts +51 -0
  643. package/src/code/index.ts +174 -0
  644. package/src/code-editor/completion.ts +161 -0
  645. package/src/code-editor/doc.ts +198 -0
  646. package/src/code-editor/hx.ts +26 -0
  647. package/src/code-editor/index.ts +615 -0
  648. package/src/code-editor/node.ts +1926 -0
  649. package/src/code-language/hljs.ts +302 -0
  650. package/src/code-language/index.ts +51 -0
  651. package/src/code-language/languages/glsl.ts +120 -0
  652. package/src/code-language/languages/javascript.ts +419 -0
  653. package/src/code-language/languages/json.ts +72 -0
  654. package/src/code-language/languages/shell.ts +221 -0
  655. package/src/code-language/languages/sql.ts +165 -0
  656. package/src/code-language/lezer.ts +190 -0
  657. package/src/code-language/registry.ts +66 -0
  658. package/src/code-language/runs.ts +128 -0
  659. package/src/code-language/stream.ts +306 -0
  660. package/src/code-language/textmate.ts +134 -0
  661. package/src/code-language/theme.ts +124 -0
  662. package/src/code-language/timers.ts +36 -0
  663. package/src/code-language/types.ts +252 -0
  664. package/src/codeblock/index.ts +146 -0
  665. package/src/color-picker/ColorField.ts +333 -0
  666. package/src/color-picker/ColorPicker.ts +1069 -0
  667. package/src/color-picker/color.ts +399 -0
  668. package/src/color-picker/index.ts +50 -0
  669. package/src/desktop-calendar/eds.ts +484 -0
  670. package/src/desktop-calendar/ical.ts +119 -0
  671. package/src/desktop-calendar/index.ts +287 -0
  672. package/src/embed/client.ts +311 -0
  673. package/src/embed/host.ts +432 -0
  674. package/src/embed/index.ts +33 -0
  675. package/src/embed/timers.ts +32 -0
  676. package/src/flow/draw.ts +418 -0
  677. package/src/flow/index.ts +473 -0
  678. package/src/flow/model.ts +687 -0
  679. package/src/flow/node.ts +3694 -0
  680. package/src/flow/paths.ts +403 -0
  681. package/src/flow/types.ts +634 -0
  682. package/src/formula/hx.ts +51 -0
  683. package/src/formula/index.ts +243 -0
  684. package/src/formula/katex.ts +209 -0
  685. package/src/formula/layout.ts +876 -0
  686. package/src/formula/node.ts +467 -0
  687. package/src/html/controls.ts +244 -0
  688. package/src/html/css/cascade.ts +606 -0
  689. package/src/html/css/parse.ts +500 -0
  690. package/src/html/css/style.ts +1371 -0
  691. package/src/html/css/ua.ts +174 -0
  692. package/src/html/css/values.ts +358 -0
  693. package/src/html/dom.ts +410 -0
  694. package/src/html/hx.ts +55 -0
  695. package/src/html/index.ts +545 -0
  696. package/src/html/layout/block.ts +876 -0
  697. package/src/html/layout/boxes.ts +981 -0
  698. package/src/html/layout/flex.ts +297 -0
  699. package/src/html/layout/floats.ts +125 -0
  700. package/src/html/layout/inline.ts +870 -0
  701. package/src/html/layout/table.ts +380 -0
  702. package/src/html/node.ts +967 -0
  703. package/src/html/paint.ts +605 -0
  704. package/src/html/resources.ts +214 -0
  705. package/src/index.ts +638 -0
  706. package/src/internal/heights.ts +211 -0
  707. package/src/internal/hx.ts +53 -0
  708. package/src/internal/text.ts +44 -0
  709. package/src/internal/timers.ts +44 -0
  710. package/src/internal/widget.ts +92 -0
  711. package/src/markdown/ast.ts +163 -0
  712. package/src/markdown/hx.ts +51 -0
  713. package/src/markdown/index.ts +686 -0
  714. package/src/markdown/parse.ts +1037 -0
  715. package/src/markdown/spans.ts +143 -0
  716. package/src/media-player/backends.ts +155 -0
  717. package/src/media-player/control.ts +70 -0
  718. package/src/media-player/hx.ts +51 -0
  719. package/src/media-player/index.ts +378 -0
  720. package/src/media-player/mpv.ts +146 -0
  721. package/src/media-player/vlc.ts +97 -0
  722. package/src/richtext/index.ts +42 -0
  723. package/src/richtext/links.ts +78 -0
  724. package/src/richtext/menu.ts +49 -0
  725. package/src/richtext/node.ts +406 -0
  726. package/src/richtext/runs.ts +253 -0
  727. package/src/table/hx.ts +51 -0
  728. package/src/table/index.ts +1219 -0
  729. package/src/table/rows.ts +275 -0
  730. package/src/terminal/backends.ts +242 -0
  731. package/src/terminal/hx.ts +51 -0
  732. package/src/terminal/index.ts +574 -0
  733. package/src/terminal/title.ts +172 -0
  734. package/src/terminal/vt/colors.ts +247 -0
  735. package/src/terminal/vt/diff.ts +467 -0
  736. package/src/terminal/vt/fonts.ts +233 -0
  737. package/src/terminal/vt/index.ts +443 -0
  738. package/src/terminal/vt/keys.ts +369 -0
  739. package/src/terminal/vt/mouse.ts +144 -0
  740. package/src/terminal/vt/node.ts +1242 -0
  741. package/src/terminal/vt/pty.ts +334 -0
  742. package/src/terminal/vt/renderer.ts +555 -0
  743. package/src/terminal/vt/timers.ts +31 -0
  744. package/src/terminal/vt/xterm.ts +193 -0
  745. package/src/terminal-output/hx.ts +51 -0
  746. package/src/terminal-output/index.ts +340 -0
  747. package/src/terminal-output/runs.ts +79 -0
  748. package/src/three/canvas.ts +451 -0
  749. package/src/three/events.ts +197 -0
  750. package/src/three/geometries.ts +429 -0
  751. package/src/three/globals.ts +34 -0
  752. package/src/three/hx.ts +22 -0
  753. package/src/three/index.ts +98 -0
  754. package/src/three/jsx-dev-runtime.ts +27 -0
  755. package/src/three/jsx-runtime.ts +38 -0
  756. package/src/three/jsx.ts +275 -0
  757. package/src/three/materials.ts +270 -0
  758. package/src/three/math.ts +585 -0
  759. package/src/three/objects.ts +397 -0
  760. package/src/three/passes.ts +90 -0
  761. package/src/three/postprocess.ts +677 -0
  762. package/src/three/raycast.ts +189 -0
  763. package/src/three/react-reconciler.d.ts +50 -0
  764. package/src/three/reconciler.ts +624 -0
  765. package/src/three/renderer-direct.ts +1055 -0
  766. package/src/three/renderer-indirect.ts +570 -0
  767. package/src/three/store.ts +207 -0
  768. package/src/timeline/hx.ts +31 -0
  769. package/src/timeline/index.ts +603 -0
  770. package/src/tray-host/hx.ts +51 -0
  771. package/src/tray-host/index.ts +331 -0
  772. package/src/tray-host/manager.ts +639 -0
  773. package/src/tray-host/notify.ts +102 -0
  774. package/src/tray-host/protocol.ts +235 -0
  775. package/src/tree/hx.ts +51 -0
  776. package/src/tree/index.ts +1087 -0
  777. package/src/tree/internal.ts +87 -0
  778. package/src/tree/rows.ts +334 -0
  779. package/src/index.d.ts +0 -2
  780. package/src/index.js +0 -9
  781. package/src/sparkline/index.d.ts +0 -29
  782. package/src/sparkline/index.js +0 -50
  783. package/src/sparkline/node.js +0 -72
@@ -0,0 +1,1926 @@
1
+ // The retained node behind `<codeeditor>`: the text model (lines, caret,
2
+ // selection, undo), scrolling, and all painting (gutter, tokens, selection,
3
+ // caret, bracket match, diagnostics). `./index.ts` registers it and wires
4
+ // input to it.
5
+ //
6
+ // **How input reaches this node.** Through the default-action seam
7
+ // react-x11#266 made public: `defaultKeyDown`, `defaultMouseDown`,
8
+ // `defaultMouseDrag`, `defaultMouseUp`, `defaultFocus` and `defaultBlur`
9
+ // are methods the EventManager calls on the focused or pressed node, after
10
+ // the application's own `onKeyDown`/`onMouseDown` handlers and not at all
11
+ // if one of them called `preventDefault()`. So a bare `<codeeditor>` in
12
+ // JSX edits, selects and blinks the way `<textinput>` does, with no
13
+ // wrapping component required — and an app still vetoes or extends any of
14
+ // it without knowing how the element is built.
15
+ //
16
+ // `<CodeEditor>` is now only the completion popup: it is an ordinary
17
+ // `onKeyDown` handler that consumes the keys the popup owns (arrows,
18
+ // Return, Tab-accept, Escape-close) and, by consuming them, keeps this
19
+ // node's default action from also acting on them.
20
+ import { CARET_BLINK_MS, Node } from 'react-x11/node';
21
+ import type {
22
+ Context2D,
23
+ MeasureConstraints,
24
+ MeasuredSize,
25
+ } from 'react-x11/node';
26
+ import type { Style } from 'react-x11/style';
27
+ import {
28
+ ctrlChordLetter,
29
+ XK_BACKSPACE,
30
+ XK_DELETE,
31
+ XK_DOWN,
32
+ XK_END,
33
+ XK_ESCAPE,
34
+ XK_HOME,
35
+ XK_KP_ENTER,
36
+ XK_LEFT,
37
+ XK_PAGE_DOWN,
38
+ XK_PAGE_UP,
39
+ XK_RETURN,
40
+ XK_RIGHT,
41
+ XK_TAB,
42
+ XK_UP,
43
+ } from 'react-x11/keysyms';
44
+
45
+ import { startInterval, stopInterval } from '../code-language/timers.js';
46
+ import type { TimerId } from '../code-language/timers.js';
47
+ import {
48
+ clampPos,
49
+ comparePos,
50
+ maxPos,
51
+ minPos,
52
+ movePos,
53
+ posEqual,
54
+ tabMap,
55
+ wordBoundary,
56
+ wordRangeAt,
57
+ } from './doc.js';
58
+ import type { TabMap } from './doc.js';
59
+ import {
60
+ DARK_TOKEN_STYLES,
61
+ isDarkBackground,
62
+ LIGHT_TOKEN_STYLES,
63
+ tokenStyleFor,
64
+ } from '../code-language/theme.js';
65
+ import type {
66
+ Diagnostic,
67
+ Language,
68
+ Position,
69
+ Selection,
70
+ Token,
71
+ TokenStyles,
72
+ Tokenizer,
73
+ } from '../code-language/types.js';
74
+
75
+ /**
76
+ * The element name — registration key, `kind`, and JSX tag, one string.
77
+ * react-x11 rejects a node whose `kind` is not the name it was registered
78
+ * under, because `kind` is what paint order, the test queries and the DEV
79
+ * assertion all match on. One constant, used in all three places.
80
+ */
81
+ export const ELEMENT = 'codeeditor';
82
+
83
+ const UNDO_LIMIT = 200;
84
+ const DEFAULT_ROWS = 6;
85
+ const PREFERRED_WIDTH = 360;
86
+ const GUTTER_PAD = 8; // each side of the line numbers
87
+ const CARET_MARGIN = 24; // keep this many px visible beside the caret
88
+ const MATCH_SCAN_LIMIT = 20000; // chars examined looking for a bracket match
89
+
90
+ /**
91
+ * The editor's imperative surface — what `ev.target` and the component's
92
+ * `ref` are typed as. An interface rather than the node class on purpose:
93
+ * the class has private state, which makes its type nominal, and a nominal
94
+ * type inside the exported props would stop `src/` and `dist/` declarations
95
+ * (both live in this repo's typecheck program) merging their JSX
96
+ * augmentations. The full retained node is underneath — cast to
97
+ * {@link CodeEditorNode} for `abs`, `style` and the rest of the `Node`
98
+ * surface.
99
+ */
100
+ export interface CodeEditorHandle {
101
+ /** Read the text; assign to set it *without* an `onChange`, the
102
+ * DOM-input contract form libraries rely on. */
103
+ value: string;
104
+ readonly name: string | undefined;
105
+ readonly selection: Selection;
106
+ readonly lines: readonly string[];
107
+ readonly language: Language | null;
108
+ readonly canUndo: boolean;
109
+ readonly canRedo: boolean;
110
+ selectedText(): string;
111
+ replaceRange(from: Position, to: Position, text: string): void;
112
+ insertText(text: string): void;
113
+ moveCaret(pos: Position, extend: boolean): void;
114
+ select(anchor: Position, head: Position): void;
115
+ selectAll(): void;
116
+ undo(): void;
117
+ redo(): void;
118
+ indentSelection(dir: 1 | -1): void;
119
+ toggleLineComment(): void;
120
+ copySelection(selection?: string): void;
121
+ pasteFrom(selection?: string): void;
122
+ scrollBy(dx: number, dy: number): boolean;
123
+ caretRect(): { x: number; y: number; height: number };
124
+ focus(): void;
125
+ blur(): void;
126
+ }
127
+
128
+ /** The value/submit/selection event the component's callbacks receive —
129
+ * the same shape core's `<textinput>` events have, minus dispatch. */
130
+ export interface CodeEditorEvent {
131
+ type: 'change' | 'submit' | 'selectionchange';
132
+ value: string;
133
+ name: string | undefined;
134
+ selection: Selection;
135
+ target: CodeEditorHandle;
136
+ currentTarget: CodeEditorHandle;
137
+ nativeEvent: unknown;
138
+ defaultPrevented: boolean;
139
+ preventDefault(): void;
140
+ stopPropagation(): void;
141
+ }
142
+
143
+ /** The props `<codeeditor>` takes. The component re-exports this. */
144
+ export interface CodeEditorProps {
145
+ /** Controlled text (with `onChange`). */
146
+ value?: string;
147
+ /** Uncontrolled initial text. */
148
+ defaultValue?: string;
149
+ onChange?: (ev: CodeEditorEvent) => void;
150
+ /** Ctrl+Enter. */
151
+ onSubmit?: (ev: CodeEditorEvent) => void;
152
+ /** Caret or selection moved — what completion UIs track. */
153
+ onSelectionChange?: (ev: CodeEditorEvent) => void;
154
+ /** Field name, echoed on events. */
155
+ name?: string;
156
+ /** The language seam. `null`/absent paints plain text. */
157
+ language?: Language | null;
158
+ /** Token type → colour/weight/italic. Default {@link LIGHT_TOKEN_STYLES}. */
159
+ tokenStyles?: TokenStyles;
160
+ /** Ranges to underline (LSP-shaped). */
161
+ diagnostics?: readonly Diagnostic[];
162
+ readOnly?: boolean;
163
+ /** Inert: no default action runs, and the component also stops making it
164
+ * focusable. `readOnly` still navigates and copies; this does neither. */
165
+ disabled?: boolean;
166
+ placeholder?: string;
167
+ placeholderColor?: string;
168
+ /** Preferred height in text lines (default 6). */
169
+ rows?: number;
170
+ /** Tab display width and indent size (default 4). */
171
+ tabSize?: number;
172
+ /** Indent with spaces (default) or a real tab. */
173
+ insertSpaces?: boolean;
174
+ lineNumbers?: boolean;
175
+ /** Tint the caret's line. */
176
+ activeLine?: boolean;
177
+ /** Highlight the bracket pair around the caret (default true). */
178
+ matchBrackets?: boolean;
179
+ selectionColor?: string;
180
+ caretColor?: string;
181
+ gutterColor?: string;
182
+ gutterBackground?: string;
183
+ activeLineColor?: string;
184
+ matchingBracketColor?: string;
185
+ style?: Style | Style[];
186
+ }
187
+
188
+ // --- structural types for runtime surfaces that have no public declarations
189
+ //
190
+ // Same policy as `desktop-calendar/ical.ts`: describe what is used, locally,
191
+ // rather than reaching into another package's internal type graph.
192
+
193
+ /** The slice of ntk's TextLayout the editor reads. */
194
+ interface LayoutLike {
195
+ width: number;
196
+ height: number;
197
+ draw(ctx: unknown, x: number, y: number): void;
198
+ caretPosition(cp: number): { x: number; y: number; height: number };
199
+ indexAt(x: number, y: number): number;
200
+ }
201
+
202
+ interface FontsLike {
203
+ layout(
204
+ content: string | Array<Record<string, unknown>>,
205
+ style: Record<string, unknown>,
206
+ options?: Record<string, unknown>,
207
+ ): LayoutLike;
208
+ }
209
+
210
+ /** ntk's clipboard, reached through the public `app` — write/read are what
211
+ * core's own fields call. Absent on older ntk; every use is optional. */
212
+ interface ClipboardLike {
213
+ write(text: string, options?: { selection?: string }): Promise<unknown>;
214
+ read(options?: { selection?: string }): Promise<unknown>;
215
+ }
216
+
217
+ interface HistoryEntry {
218
+ value: string;
219
+ caret: Position;
220
+ anchor: Position;
221
+ }
222
+
223
+ const BRACKETS: Record<string, { match: string; dir: 1 | -1 }> = {
224
+ '(': { match: ')', dir: 1 },
225
+ '[': { match: ']', dir: 1 },
226
+ '{': { match: '}', dir: 1 },
227
+ ')': { match: '(', dir: -1 },
228
+ ']': { match: '[', dir: -1 },
229
+ '}': { match: '{', dir: -1 },
230
+ };
231
+
232
+ /** Token types the bracket matcher must not look inside. */
233
+ const OPAQUE_TOKENS = new Set(['string', 'string2', 'comment', 'docComment']);
234
+
235
+ const SEVERITY_COLORS: Record<string, string> = {
236
+ error: '#e5484d',
237
+ warning: '#d5a021',
238
+ information: '#3b82f6',
239
+ hint: '#8b8f98',
240
+ };
241
+
242
+ function cpToUtf16(text: string, cp: number): number {
243
+ let i = 0;
244
+ for (let n = 0; n < cp && i < text.length; n++) {
245
+ const code = text.codePointAt(i)!;
246
+ i += code > 0xffff ? 2 : 1;
247
+ }
248
+ return i;
249
+ }
250
+
251
+ function utf16ToCp(text: string, index: number): number {
252
+ let cp = 0;
253
+ for (let i = 0; i < index && i < text.length;) {
254
+ const code = text.codePointAt(i)!;
255
+ i += code > 0xffff ? 2 : 1;
256
+ cp++;
257
+ }
258
+ return cp;
259
+ }
260
+
261
+ /** What the component's handlers pass in — the public synthetic event
262
+ * surface (docs/events.md), structurally. */
263
+ export interface EditorKeyEvent {
264
+ keysym?: number;
265
+ codepoint?: number;
266
+ key?: string;
267
+ shiftKey: boolean;
268
+ ctrlKey: boolean;
269
+ nativeEvent?: unknown;
270
+ defaultPrevented?: boolean;
271
+ /** Consumes the key: what it suppresses is the default action *after*
272
+ * this one, which for Tab is the focus cycle. */
273
+ preventDefault?(): void;
274
+ }
275
+
276
+ export interface EditorMouseEvent {
277
+ x: number;
278
+ y: number;
279
+ button?: number;
280
+ detail?: number;
281
+ shiftKey: boolean;
282
+ capturePointer?(): void;
283
+ releasePointer?(): void;
284
+ preventDefault?(): void;
285
+ }
286
+
287
+ interface LineCacheEntry {
288
+ display: string;
289
+ tokens: readonly Token[];
290
+ styleKey: string;
291
+ layout: LayoutLike | null;
292
+ map: TabMap;
293
+ }
294
+
295
+ export class CodeEditorNode extends Node implements CodeEditorHandle {
296
+ private _lines: string[];
297
+ /** What `_lines` was last built from — how a controlled `value` that the
298
+ * parent refused to update snaps back on the next read (see `_syncProps`). */
299
+ private _synced: string;
300
+ private _caret: Position = { line: 0, ch: 0 };
301
+ private _anchor: Position = { line: 0, ch: 0 };
302
+ private _scrollX = 0;
303
+ private _scrollY = 0;
304
+ private _focused = false;
305
+ private _caretOn = false;
306
+ private _blinkTimer: TimerId = null;
307
+ private _goalX: number | null = null;
308
+ private _drag: 'char' | 'word' | 'line' | null = null;
309
+ private _dragOrigin: Position = { line: 0, ch: 0 };
310
+ /** Escape was the last key, so the next Tab leaves instead of indenting —
311
+ * the editor's way of not being a keyboard trap (see `defaultKeyDown`). */
312
+ private _tabEscapes = false;
313
+ private _history: HistoryEntry[];
314
+ private _historyIndex = 0;
315
+ private _undoRun: string | null = null;
316
+ private _pendingValue: string | null = null;
317
+ private _keyNative: unknown = null;
318
+ private _tok: Tokenizer | null = null;
319
+ private _tokLanguage: Language | null | undefined;
320
+ private _lineCache = new Map<number, LineCacheEntry>();
321
+ private _metricsKey = '';
322
+ private _lineH = 0;
323
+ private _charW = 0;
324
+ private _widest = 0;
325
+
326
+ constructor(props: Record<string, unknown>, app: unknown) {
327
+ super(ELEMENT, props, app as ConstructorParameters<typeof Node>[2]);
328
+ const initial =
329
+ props.value != null
330
+ ? String(props.value)
331
+ : props.defaultValue != null
332
+ ? String(props.defaultValue)
333
+ : '';
334
+ this._lines = initial.split('\n');
335
+ this._synced = initial;
336
+ this._history = [
337
+ { value: initial, caret: this._caret, anchor: this._anchor },
338
+ ];
339
+ // Without this nothing focuses the editor and no key ever reaches it —
340
+ // an app's `focusable`/`tabIndex` prop still overrides either way, which
341
+ // is how `<CodeEditor disabled>` stays out of the tab order.
342
+ this.focusableByDefault = true;
343
+ this.defaultCursor = 'text';
344
+ // The editor's selection is its own (react-x11#291). A `selectable`
345
+ // document around it — a `<Markdown>` with a live editor in it, a form
346
+ // on a selectable pane — skips this subtree whole rather than lighting
347
+ // up half the text somebody is typing in, and a press here never
348
+ // reaches the surface above. That is the same rule `<textinput>` keeps,
349
+ // and the reason the `default*` handlers below can own the gesture
350
+ // without calling `super`.
351
+ this.hasOwnSelection = true;
352
+ }
353
+
354
+ /**
355
+ * The editor's own size: a comfortable width, never wider than what is on
356
+ * offer, and `rows` text lines tall. An unbounded axis arrives as
357
+ * `Infinity`, so the `Math.min` is the whole rule. The height comes from a
358
+ * prop rather than from the style, which is what makes the
359
+ * `invalidateMeasure` in `applyProps` necessary.
360
+ */
361
+ override measureContent({ width }: MeasureConstraints): MeasuredSize {
362
+ const rows = Math.max(1, Number(this.props.rows ?? DEFAULT_ROWS));
363
+ return {
364
+ width: Math.min(PREFERRED_WIDTH, width),
365
+ height: Math.ceil(this._lineHeight() * rows),
366
+ };
367
+ }
368
+
369
+ // --- value & props sync --------------------------------------------------
370
+
371
+ get value(): string {
372
+ if (this._pendingValue !== null) return this._pendingValue;
373
+ if (this.props.value != null) return String(this.props.value);
374
+ return this._lines.join('\n');
375
+ }
376
+
377
+ /** Programmatic set, no `onChange` — the DOM-input contract form libraries
378
+ * rely on. On a controlled editor `props.value` wins the next render. */
379
+ set value(next: string) {
380
+ const text = next == null ? '' : String(next);
381
+ if (text === this._lines.join('\n')) return;
382
+ this._setText(text);
383
+ this._noteExternal();
384
+ this._repaint();
385
+ }
386
+
387
+ get name(): string | undefined {
388
+ return this.props.name as string | undefined;
389
+ }
390
+
391
+ get selection(): Selection {
392
+ return { anchor: { ...this._anchor }, head: { ...this._caret } };
393
+ }
394
+
395
+ get lines(): readonly string[] {
396
+ this._syncProps();
397
+ return this._lines;
398
+ }
399
+
400
+ get language(): Language | null {
401
+ return (this.props.language as Language | null | undefined) ?? null;
402
+ }
403
+
404
+ /**
405
+ * Pull a controlled `value` into the model. Called at the top of every
406
+ * command and paint: a parent that ignored an `onChange` never re-renders,
407
+ * so there is no `applyProps` moment to catch the divergence — the next
408
+ * read is that moment.
409
+ */
410
+ private _syncProps(): void {
411
+ const v = this.props.value;
412
+ if (v == null) return;
413
+ const text = String(v);
414
+ if (text === this._synced) return;
415
+ this._setText(text);
416
+ this._noteExternal();
417
+ }
418
+
419
+ private _setText(text: string): void {
420
+ this._lines = text.split('\n');
421
+ this._synced = text;
422
+ this._caret = clampPos(this._lines, this._caret);
423
+ this._anchor = clampPos(this._lines, this._anchor);
424
+ this._lineCache.clear();
425
+ this._widest = 0;
426
+ this._tokenizer()?.setLines(this._lines);
427
+ }
428
+
429
+ // --- tokenizer -----------------------------------------------------------
430
+
431
+ private _tokenizer(): Tokenizer | null {
432
+ const lang = this.language;
433
+ if (lang !== this._tokLanguage) {
434
+ this._tok?.dispose?.();
435
+ this._tokLanguage = lang;
436
+ this._tok = lang
437
+ ? lang.createTokenizer({
438
+ invalidate: (fromLine) => {
439
+ for (const key of this._lineCache.keys()) {
440
+ if (key >= fromLine) this._lineCache.delete(key);
441
+ }
442
+ this._repaint();
443
+ },
444
+ })
445
+ : null;
446
+ this._tok?.setLines(this._lines);
447
+ this._lineCache.clear();
448
+ }
449
+ return this._tok;
450
+ }
451
+
452
+ // --- metrics & style -----------------------------------------------------
453
+
454
+ private _textStyle(): Record<string, unknown> {
455
+ const s = this.style as Record<string, unknown>;
456
+ return {
457
+ family: (s.fontFamily as string) ?? 'monospace',
458
+ size: (s.fontSize as number) ?? 13,
459
+ weight: (s.fontWeight as number) ?? 400,
460
+ style: (s.fontStyle as string) ?? 'normal',
461
+ // theme text before hardcoded ink: on a dark desktop the palette's
462
+ // `text` is light, and a near-black fallback would vanish — the
463
+ // component's default style also asks for `color: '$text'`, so this
464
+ // branch is for a bare <codeeditor> under a themeless tree
465
+ color: (s.color as string) ?? this._themeColor('text') ?? '#24292f',
466
+ };
467
+ }
468
+
469
+ private _themeColor(token: string): string | undefined {
470
+ const theme = this.theme as Record<string, unknown> | null;
471
+ const v = theme?.[token];
472
+ return typeof v === 'string' ? v : undefined;
473
+ }
474
+
475
+ private _fonts(): FontsLike | null {
476
+ return (
477
+ (this.app as { fonts?: FontsLike } | null | undefined)?.fonts ?? null
478
+ );
479
+ }
480
+
481
+ private _metrics(): void {
482
+ const s = this._textStyle();
483
+ const key = `${s.family}|${s.size}|${s.weight}`;
484
+ if (key === this._metricsKey && this._lineH > 0) return;
485
+ const fonts = this._fonts();
486
+ this._metricsKey = key;
487
+ if (!fonts) {
488
+ this._lineH = Number(s.size) * 1.4;
489
+ this._charW = Number(s.size) * 0.6;
490
+ return;
491
+ }
492
+ const probe = fonts.layout('Mg', s);
493
+ this._lineH = probe.height || Number(s.size) * 1.4;
494
+ this._charW = fonts.layout('0', s).width || Number(s.size) * 0.6;
495
+ }
496
+
497
+ private _lineHeight(): number {
498
+ this._metrics();
499
+ return this._lineH;
500
+ }
501
+
502
+ /**
503
+ * The content box: `abs` inset by border and padding, computed from the
504
+ * flattened style. [react-x11 gap] built-in fields read an internal
505
+ * `contentBox()`; a public equivalent would remove this arithmetic —
506
+ * filed as react-x11#254.
507
+ */
508
+ private _contentRect(): {
509
+ x: number;
510
+ y: number;
511
+ width: number;
512
+ height: number;
513
+ } {
514
+ const s = this.style as Record<string, unknown>;
515
+ const num = (v: unknown): number => (typeof v === 'number' ? v : 0);
516
+ const border = num(s.borderWidth);
517
+ const pad = num(s.padding);
518
+ const top = num(s.borderTopWidth ?? border) + num(s.paddingTop ?? pad);
519
+ const right =
520
+ num(s.borderRightWidth ?? border) + num(s.paddingRight ?? pad);
521
+ const bottom =
522
+ num(s.borderBottomWidth ?? border) + num(s.paddingBottom ?? pad);
523
+ const left = num(s.borderLeftWidth ?? border) + num(s.paddingLeft ?? pad);
524
+ const { x, y, width, height } = this.abs;
525
+ return {
526
+ x: x + left,
527
+ y: y + top,
528
+ width: Math.max(0, width - left - right),
529
+ height: Math.max(0, height - top - bottom),
530
+ };
531
+ }
532
+
533
+ private _gutterWidth(): number {
534
+ if (!this.props.lineNumbers) return 0;
535
+ this._metrics();
536
+ const digits = Math.max(2, String(this._lines.length).length);
537
+ return Math.ceil(digits * this._charW) + GUTTER_PAD * 2;
538
+ }
539
+
540
+ private _tabSize(): number {
541
+ const n = Number(this.props.tabSize ?? 4);
542
+ return Number.isFinite(n) && n >= 1 ? Math.floor(n) : 4;
543
+ }
544
+
545
+ // --- per-line layout -----------------------------------------------------
546
+
547
+ private _resolveColor(color: string | undefined): string | undefined {
548
+ if (!color) return color;
549
+ if (color.startsWith('$')) return this._themeColor(color.slice(1));
550
+ return color;
551
+ }
552
+
553
+ /**
554
+ * The token palette in force: the prop when given, else whichever
555
+ * built-in set reads on this editor's background — the resolved
556
+ * `backgroundColor` when it is a judgeable colour, the theme's
557
+ * `background` otherwise. This is what makes one `<CodeEditor>` legible
558
+ * on a light *and* a dark desktop with no props at all.
559
+ */
560
+ private _tokenStyles(): TokenStyles {
561
+ const explicit = this.props.tokenStyles as TokenStyles | undefined;
562
+ if (explicit) return explicit;
563
+ const s = this.style as Record<string, unknown>;
564
+ const own = isDarkBackground(s.backgroundColor as string | undefined);
565
+ const dark = own ?? isDarkBackground(this._themeColor('background'));
566
+ return dark ? DARK_TOKEN_STYLES : LIGHT_TOKEN_STYLES;
567
+ }
568
+
569
+ /** Shared by the paint defaults that need a light/dark answer. */
570
+ private _onDark(): boolean {
571
+ const s = this.style as Record<string, unknown>;
572
+ return (
573
+ (isDarkBackground(s.backgroundColor as string | undefined) ??
574
+ isDarkBackground(this._themeColor('background'))) === true
575
+ );
576
+ }
577
+
578
+ private _lineEntry(line: number): LineCacheEntry {
579
+ const raw = this._lines[line];
580
+ const map = tabMap(raw, this._tabSize());
581
+ const tokens = this._tokenizer()?.lineTokens(line) ?? [];
582
+ const styleKey = this._metricsKey;
583
+ const cached = this._lineCache.get(line);
584
+ if (
585
+ cached &&
586
+ cached.display === map.display &&
587
+ cached.tokens === tokens &&
588
+ cached.styleKey === styleKey
589
+ ) {
590
+ return cached;
591
+ }
592
+ const fonts = this._fonts();
593
+ const base = this._textStyle();
594
+ let layout: LayoutLike | null = null;
595
+ if (fonts) {
596
+ const styles = this._tokenStyles();
597
+ const spans: Array<Record<string, unknown>> = [];
598
+ let cursor = 0;
599
+ for (const t of tokens) {
600
+ const from = map.toDisplay(t.from);
601
+ const to = map.toDisplay(t.to);
602
+ if (from > cursor) {
603
+ spans.push({ text: map.display.slice(cursor, from) });
604
+ }
605
+ if (to > from) {
606
+ const st = tokenStyleFor(styles, t.type);
607
+ const span: Record<string, unknown> = {
608
+ text: map.display.slice(from, to),
609
+ };
610
+ if (st) {
611
+ const color = this._resolveColor(st.color);
612
+ if (color) span.color = color;
613
+ if (st.weight) span.weight = st.weight;
614
+ if (st.fontStyle) span.style = st.fontStyle;
615
+ }
616
+ spans.push(span);
617
+ }
618
+ cursor = Math.max(cursor, to);
619
+ }
620
+ if (cursor < map.display.length) {
621
+ spans.push({ text: map.display.slice(cursor) });
622
+ }
623
+ layout = fonts.layout(spans.length > 0 ? spans : [{ text: '' }], base);
624
+ }
625
+ const entry: LineCacheEntry = {
626
+ display: map.display,
627
+ tokens,
628
+ styleKey,
629
+ layout,
630
+ map,
631
+ };
632
+ this._lineCache.set(line, entry);
633
+ if (layout && layout.width > this._widest) this._widest = layout.width;
634
+ return entry;
635
+ }
636
+
637
+ /** Caret x (display pixels from the text origin) for a position. */
638
+ private _caretX(pos: Position): number {
639
+ const entry = this._lineEntry(pos.line);
640
+ if (!entry.layout) return 0;
641
+ const disp = entry.map.toDisplay(pos.ch);
642
+ return entry.layout.caretPosition(utf16ToCp(entry.display, disp)).x;
643
+ }
644
+
645
+ /** Window coordinates → document position. */
646
+ private _posAt(x: number, y: number): Position {
647
+ this._metrics();
648
+ const content = this._contentRect();
649
+ const textX = content.x + this._gutterWidth();
650
+ const line = Math.max(
651
+ 0,
652
+ Math.min(
653
+ this._lines.length - 1,
654
+ Math.floor((y - content.y + this._scrollY) / this._lineH),
655
+ ),
656
+ );
657
+ const entry = this._lineEntry(line);
658
+ if (!entry.layout) return { line, ch: 0 };
659
+ const cp = entry.layout.indexAt(x - textX + this._scrollX, 0);
660
+ const disp = cpToUtf16(entry.display, cp);
661
+ return clampPos(this._lines, { line, ch: entry.map.toRaw(disp) });
662
+ }
663
+
664
+ /** Font facts the completion popup sizes itself with. */
665
+ metrics(): {
666
+ lineHeight: number;
667
+ charWidth: number;
668
+ family: string;
669
+ size: number;
670
+ } {
671
+ this._metrics();
672
+ const s = this._textStyle();
673
+ return {
674
+ lineHeight: this._lineH,
675
+ charWidth: this._charW,
676
+ family: String(s.family),
677
+ size: Number(s.size),
678
+ };
679
+ }
680
+
681
+ /** Text width in this editor's font. The built-in completion popup no
682
+ * longer needs it — a `<popup anchor>` measures its own rows — but an app
683
+ * laying out its own overlay against the text still does. */
684
+ measureText(text: string): number {
685
+ const fonts = this._fonts();
686
+ if (!fonts) return text.length * this._charW;
687
+ return fonts.layout(text, this._textStyle()).width;
688
+ }
689
+
690
+ /** Caret geometry relative to this node's origin — node coordinates, which
691
+ * is exactly what a `<popup anchor={{at}}>` takes, so the completion popup
692
+ * hands this straight through and nothing here has to know where the
693
+ * editor sits on screen. */
694
+ caretRect(): { x: number; y: number; height: number } {
695
+ this._syncProps();
696
+ this._metrics();
697
+ const content = this._contentRect();
698
+ const textX = content.x + this._gutterWidth() - this.abs.x;
699
+ return {
700
+ x: textX + this._caretX(this._caret) - this._scrollX,
701
+ y:
702
+ content.y - this.abs.y + this._caret.line * this._lineH - this._scrollY,
703
+ height: this._lineH,
704
+ };
705
+ }
706
+
707
+ // --- scrolling -----------------------------------------------------------
708
+
709
+ private _maxScrollY(): number {
710
+ const content = this._contentRect();
711
+ return Math.max(
712
+ 0,
713
+ this._lines.length * this._lineHeight() - content.height,
714
+ );
715
+ }
716
+
717
+ private _maxScrollX(): number {
718
+ const content = this._contentRect();
719
+ const textW = content.width - this._gutterWidth();
720
+ return Math.max(0, this._widest + CARET_MARGIN - textW);
721
+ }
722
+
723
+ /** Wheel notches (deltaX/deltaY arrive as ±1 per notch) → pixels. */
724
+ handleWheel(ev: { deltaX: number; deltaY: number }): boolean {
725
+ const step = this._lineHeight() * 3;
726
+ return this.scrollBy(ev.deltaX * step, ev.deltaY * step);
727
+ }
728
+
729
+ /** Returns true when the offset moved (the component consumes the wheel
730
+ * event exactly then, so an unscrollable editor lets the page scroll). */
731
+ scrollBy(dx: number, dy: number): boolean {
732
+ const nx = Math.max(0, Math.min(this._scrollX + dx, this._maxScrollX()));
733
+ const ny = Math.max(0, Math.min(this._scrollY + dy, this._maxScrollY()));
734
+ if (nx === this._scrollX && ny === this._scrollY) return false;
735
+ this._scrollX = nx;
736
+ this._scrollY = ny;
737
+ this.root?.invalidate(false, this.abs, 'scroll');
738
+ return true;
739
+ }
740
+
741
+ private _ensureCaretVisible(): void {
742
+ this._metrics();
743
+ const content = this._contentRect();
744
+ const lineTop = this._caret.line * this._lineH;
745
+ if (lineTop < this._scrollY) this._scrollY = lineTop;
746
+ const lineBottom = lineTop + this._lineH;
747
+ if (lineBottom > this._scrollY + content.height) {
748
+ this._scrollY = lineBottom - content.height;
749
+ }
750
+ const textW = content.width - this._gutterWidth();
751
+ const x = this._caretX(this._caret);
752
+ if (x < this._scrollX + CARET_MARGIN) {
753
+ this._scrollX = Math.max(0, x - CARET_MARGIN);
754
+ }
755
+ if (x > this._scrollX + textW - CARET_MARGIN) {
756
+ this._scrollX = x - textW + CARET_MARGIN;
757
+ }
758
+ this._scrollY = Math.max(0, Math.min(this._scrollY, this._maxScrollY()));
759
+ this._scrollX = Math.max(0, this._scrollX);
760
+ }
761
+
762
+ // --- events out ----------------------------------------------------------
763
+
764
+ private _makeEvent(type: CodeEditorEvent['type']): CodeEditorEvent {
765
+ const self = this;
766
+ const ev: CodeEditorEvent = {
767
+ type,
768
+ value: this.value,
769
+ name: this.name,
770
+ selection: this.selection,
771
+ target: self,
772
+ currentTarget: self,
773
+ nativeEvent: this._keyNative ?? null,
774
+ defaultPrevented: false,
775
+ preventDefault() {
776
+ ev.defaultPrevented = true;
777
+ },
778
+ stopPropagation() {},
779
+ };
780
+ return ev;
781
+ }
782
+
783
+ private _fire(
784
+ prop: 'onChange' | 'onSubmit' | 'onSelectionChange',
785
+ value: string,
786
+ ): void {
787
+ const handler = this.props[prop] as
788
+ ((ev: CodeEditorEvent) => void) | undefined;
789
+ if (!handler) return;
790
+ const previous = this._pendingValue;
791
+ this._pendingValue = value;
792
+ try {
793
+ handler(
794
+ this._makeEvent(
795
+ prop === 'onSubmit'
796
+ ? 'submit'
797
+ : prop === 'onSelectionChange'
798
+ ? 'selectionchange'
799
+ : 'change',
800
+ ),
801
+ );
802
+ } finally {
803
+ this._pendingValue = previous;
804
+ }
805
+ }
806
+
807
+ private _repaint(): void {
808
+ this._caretOn = true;
809
+ this.root?.invalidate(false, this.abs, 'text');
810
+ }
811
+
812
+ // --- history -------------------------------------------------------------
813
+
814
+ private _noteExternal(): void {
815
+ this._undoRun = null;
816
+ const value = this._lines.join('\n');
817
+ const top = this._history[this._historyIndex];
818
+ if (top && top.value === value) return;
819
+ this._pushHistory({ value, caret: this._caret, anchor: this._anchor });
820
+ }
821
+
822
+ private _pushHistory(entry: HistoryEntry): void {
823
+ this._history.length = this._historyIndex + 1;
824
+ this._history.push(entry);
825
+ if (this._history.length > UNDO_LIMIT) {
826
+ this._history.splice(0, this._history.length - UNDO_LIMIT);
827
+ }
828
+ this._historyIndex = this._history.length - 1;
829
+ }
830
+
831
+ breakUndoRun(): void {
832
+ this._undoRun = null;
833
+ }
834
+
835
+ get canUndo(): boolean {
836
+ return this._historyIndex > 0;
837
+ }
838
+
839
+ get canRedo(): boolean {
840
+ return this._historyIndex < this._history.length - 1;
841
+ }
842
+
843
+ undo(): void {
844
+ this._syncProps();
845
+ if (!this.canUndo) return;
846
+ this._historyIndex--;
847
+ this._applyHistory(this._history[this._historyIndex]);
848
+ }
849
+
850
+ redo(): void {
851
+ this._syncProps();
852
+ if (!this.canRedo) return;
853
+ this._historyIndex++;
854
+ this._applyHistory(this._history[this._historyIndex]);
855
+ }
856
+
857
+ private _applyHistory(entry: HistoryEntry): void {
858
+ this._undoRun = null;
859
+ this._goalX = null;
860
+ this._applyLines(entry.value.split('\n'), null);
861
+ this._caret = clampPos(this._lines, entry.caret);
862
+ this._anchor = clampPos(this._lines, entry.anchor);
863
+ this._afterEdit(entry.value);
864
+ }
865
+
866
+ // --- editing core --------------------------------------------------------
867
+
868
+ private _selectionRange(): [Position, Position] {
869
+ return [
870
+ minPos(this._anchor, this._caret),
871
+ maxPos(this._anchor, this._caret),
872
+ ];
873
+ }
874
+
875
+ private _hasSelection(): boolean {
876
+ return !posEqual(this._anchor, this._caret);
877
+ }
878
+
879
+ override selectedText(): string {
880
+ this._syncProps();
881
+ const [a, b] = this._selectionRange();
882
+ if (posEqual(a, b)) return '';
883
+ if (a.line === b.line) return this._lines[a.line].slice(a.ch, b.ch);
884
+ const parts = [this._lines[a.line].slice(a.ch)];
885
+ for (let i = a.line + 1; i < b.line; i++) parts.push(this._lines[i]);
886
+ parts.push(this._lines[b.line].slice(0, b.ch));
887
+ return parts.join('\n');
888
+ }
889
+
890
+ /**
891
+ * The one place text changes. Replaces `[from, to)` with `text`, moves the
892
+ * caret to the end of the insertion, fires `onChange`, and manages undo —
893
+ * `run` names a coalescable edit kind (`'type'`, `'delete-back'`,
894
+ * `'delete-forward'`); anything else is its own undo step.
895
+ */
896
+ replaceRange(
897
+ from: Position,
898
+ to: Position,
899
+ text: string,
900
+ run: string | null = null,
901
+ ): void {
902
+ this._syncProps();
903
+ if (this.props.readOnly) return;
904
+ const a = clampPos(this._lines, minPos(from, to));
905
+ const b = clampPos(this._lines, maxPos(from, to));
906
+ const insert = String(text).replace(/\r\n?/g, '\n');
907
+ const before = this._lines[a.line].slice(0, a.ch);
908
+ const after = this._lines[b.line].slice(b.ch);
909
+ const inserted = insert.split('\n');
910
+ inserted[0] = before + inserted[0];
911
+ const caretCh = inserted[inserted.length - 1].length;
912
+ inserted[inserted.length - 1] += after;
913
+ const newLines = this._lines.slice();
914
+ newLines.splice(a.line, b.line - a.line + 1, ...inserted);
915
+
916
+ const coalesce =
917
+ run !== null &&
918
+ run === this._undoRun &&
919
+ this._historyIndex === this._history.length - 1;
920
+ this._applyLines(newLines, {
921
+ fromLine: a.line,
922
+ removed: b.line - a.line + 1,
923
+ inserted: inserted.length,
924
+ });
925
+ this._caret = clampPos(this._lines, {
926
+ line: a.line + inserted.length - 1,
927
+ ch: caretCh,
928
+ });
929
+ this._anchor = { ...this._caret };
930
+ this._goalX = null;
931
+
932
+ const value = this._lines.join('\n');
933
+ if (coalesce) {
934
+ this._history[this._historyIndex] = {
935
+ value,
936
+ caret: this._caret,
937
+ anchor: this._anchor,
938
+ };
939
+ } else {
940
+ this._pushHistory({ value, caret: this._caret, anchor: this._anchor });
941
+ }
942
+ this._undoRun = run;
943
+ this._afterEdit(value);
944
+ }
945
+
946
+ private _applyLines(
947
+ newLines: string[],
948
+ edit: { fromLine: number; removed: number; inserted: number } | null,
949
+ ): void {
950
+ if (edit) {
951
+ // A tokenizer keeps the array `setLines` handed it (that is the
952
+ // contract in `Tokenizer`) and is told about changes through `edit()`
953
+ // alone, so an incremental edit has to *mutate* the live array —
954
+ // swapping in a fresh one would leave every engine tokenizing the
955
+ // pre-edit text at the new line numbers, painting a split line with
956
+ // its neighbour's colours. Callers only touch the edited span, so
957
+ // splicing that span across reproduces `newLines` exactly.
958
+ this._lines.splice(
959
+ edit.fromLine,
960
+ edit.removed,
961
+ ...newLines.slice(edit.fromLine, edit.fromLine + edit.inserted),
962
+ );
963
+ } else {
964
+ this._lines = newLines;
965
+ }
966
+ const tok = this._tokenizer();
967
+ if (edit && tok) {
968
+ tok.edit(edit);
969
+ // line indices at and below the edit moved or changed; drop them
970
+ for (const key of [...this._lineCache.keys()]) {
971
+ if (key >= edit.fromLine) this._lineCache.delete(key);
972
+ }
973
+ if (edit.removed !== edit.inserted) this._lineCache.clear();
974
+ } else {
975
+ tok?.setLines(this._lines);
976
+ this._lineCache.clear();
977
+ }
978
+ }
979
+
980
+ private _afterEdit(value: string): void {
981
+ this._synced = value;
982
+ this._ensureCaretVisible();
983
+ this._repaint();
984
+ this._fire('onChange', value);
985
+ this._fire('onSelectionChange', value);
986
+ }
987
+
988
+ insertText(text: string, run: string | null = null): void {
989
+ const [a, b] = this._selectionRange();
990
+ this.replaceRange(a, b, text, this._hasSelection() ? null : run);
991
+ }
992
+
993
+ moveCaret(pos: Position, extend: boolean): void {
994
+ this._syncProps();
995
+ this.breakUndoRun();
996
+ this._caret = clampPos(this._lines, pos);
997
+ if (!extend) this._anchor = { ...this._caret };
998
+ this._ensureCaretVisible();
999
+ this._repaint();
1000
+ this._fire('onSelectionChange', this.value);
1001
+ }
1002
+
1003
+ select(anchor: Position, head: Position): void {
1004
+ this._syncProps();
1005
+ this.breakUndoRun();
1006
+ this._anchor = clampPos(this._lines, anchor);
1007
+ this._caret = clampPos(this._lines, head);
1008
+ this._ensureCaretVisible();
1009
+ this._repaint();
1010
+ this._fire('onSelectionChange', this.value);
1011
+ }
1012
+
1013
+ // `: this`, matching the base declaration core grew in #294 — a subclass
1014
+ // narrowing it to void stops being structurally a DrawnNode.
1015
+ override selectAll(): this {
1016
+ const last = this._lines.length - 1;
1017
+ this.select(
1018
+ { line: 0, ch: 0 },
1019
+ { line: last, ch: this._lines[last].length },
1020
+ );
1021
+ this._ownPrimary();
1022
+ return this;
1023
+ }
1024
+
1025
+ // --- clipboard -----------------------------------------------------------
1026
+
1027
+ private _clipboard(): ClipboardLike | null {
1028
+ const app = this.app as { clipboard?: ClipboardLike } | null | undefined;
1029
+ return app?.clipboard ?? null;
1030
+ }
1031
+
1032
+ copySelection(selection = 'CLIPBOARD'): void {
1033
+ const text = this.selectedText();
1034
+ if (!text) return;
1035
+ this._clipboard()
1036
+ ?.write(text, { selection })
1037
+ .catch(() => {});
1038
+ }
1039
+
1040
+ private _ownPrimary(): void {
1041
+ if (this._hasSelection()) this.copySelection('PRIMARY');
1042
+ }
1043
+
1044
+ pasteFrom(selection = 'CLIPBOARD'): void {
1045
+ this._clipboard()
1046
+ ?.read({ selection })
1047
+ .then((text) => {
1048
+ if (!this.destroyed && typeof text === 'string' && text) {
1049
+ this.insertText(text);
1050
+ }
1051
+ })
1052
+ .catch(() => {});
1053
+ }
1054
+
1055
+ // --- indentation & comments ---------------------------------------------
1056
+
1057
+ private _indentUnit(): string {
1058
+ return this.props.insertSpaces === false
1059
+ ? '\t'
1060
+ : ' '.repeat(this._tabSize());
1061
+ }
1062
+
1063
+ /** Tab with a multi-line selection (or dir < 0): shift the lines. */
1064
+ indentSelection(dir: 1 | -1): void {
1065
+ this._syncProps();
1066
+ if (this.props.readOnly) return;
1067
+ const [a, b] = this._selectionRange();
1068
+ const endLine = b.line > a.line && b.ch === 0 ? b.line - 1 : b.line;
1069
+ const unit = this._indentUnit();
1070
+ const size = this._tabSize();
1071
+ const newLines = this._lines.slice();
1072
+ for (let i = a.line; i <= endLine; i++) {
1073
+ const line = newLines[i];
1074
+ if (dir > 0) {
1075
+ if (line.length > 0) newLines[i] = unit + line;
1076
+ } else {
1077
+ if (line.startsWith('\t')) newLines[i] = line.slice(1);
1078
+ else {
1079
+ let strip = 0;
1080
+ while (strip < size && line.charAt(strip) === ' ') strip++;
1081
+ newLines[i] = line.slice(strip);
1082
+ }
1083
+ }
1084
+ }
1085
+ this._replaceAllPreservingSelection(newLines, a.line, endLine);
1086
+ }
1087
+
1088
+ toggleLineComment(): void {
1089
+ this._syncProps();
1090
+ if (this.props.readOnly) return;
1091
+ const marker = this.language?.data?.lineComment;
1092
+ if (!marker) return;
1093
+ const [a, b] = this._selectionRange();
1094
+ const endLine = b.line > a.line && b.ch === 0 ? b.line - 1 : b.line;
1095
+ const targets: number[] = [];
1096
+ for (let i = a.line; i <= endLine; i++) {
1097
+ if (this._lines[i].trim().length > 0) targets.push(i);
1098
+ }
1099
+ if (targets.length === 0) return;
1100
+ const allCommented = targets.every((i) =>
1101
+ this._lines[i].trimStart().startsWith(marker),
1102
+ );
1103
+ const newLines = this._lines.slice();
1104
+ for (const i of targets) {
1105
+ const line = newLines[i];
1106
+ const indent = line.length - line.trimStart().length;
1107
+ if (allCommented) {
1108
+ const rest = line.slice(indent + marker.length);
1109
+ newLines[i] =
1110
+ line.slice(0, indent) + (rest.startsWith(' ') ? rest.slice(1) : rest);
1111
+ } else {
1112
+ newLines[i] = line.slice(0, indent) + marker + ' ' + line.slice(indent);
1113
+ }
1114
+ }
1115
+ this._replaceAllPreservingSelection(newLines, a.line, endLine);
1116
+ }
1117
+
1118
+ /** Line-rewriting commands share this: one undo step, selection kept on
1119
+ * the same lines (clamped to their new lengths). */
1120
+ private _replaceAllPreservingSelection(
1121
+ newLines: string[],
1122
+ fromLine: number,
1123
+ toLine: number,
1124
+ ): void {
1125
+ const caret = this._caret;
1126
+ const anchor = this._anchor;
1127
+ this._applyLines(newLines, {
1128
+ fromLine,
1129
+ removed: toLine - fromLine + 1,
1130
+ inserted: toLine - fromLine + 1,
1131
+ });
1132
+ this._caret = clampPos(this._lines, caret);
1133
+ this._anchor = clampPos(this._lines, anchor);
1134
+ this._undoRun = null;
1135
+ const value = this._lines.join('\n');
1136
+ this._pushHistory({ value, caret: this._caret, anchor: this._anchor });
1137
+ this._afterEdit(value);
1138
+ }
1139
+
1140
+ // --- input: keyboard -----------------------------------------------------
1141
+
1142
+ /**
1143
+ * The editor's own key behaviour, run by the EventManager after the app's
1144
+ * `onKeyDown` handlers and not at all if one of them called
1145
+ * `preventDefault()` — so `<CodeEditor>`'s completion popup vetoes this
1146
+ * simply by consuming the key first, and so does an application handler.
1147
+ *
1148
+ * Tab is an ordinary key here, ahead of focus traversal, which is what
1149
+ * lets a bare `<codeeditor>` indent with it. Owning Tab means owning the
1150
+ * keyboard user's way out, hence the convention `docs/extending.md` asks
1151
+ * elements to converge on: **Escape arms one pass-through Tab**. Escape on
1152
+ * its own does nothing visible, the next Tab leaves, and the one after
1153
+ * that indents again.
1154
+ */
1155
+ override defaultKeyDown(ev: EditorKeyEvent): void {
1156
+ if (this.props.disabled) return;
1157
+ const k = ev.keysym;
1158
+ if (k === XK_ESCAPE) {
1159
+ this._tabEscapes = true;
1160
+ return;
1161
+ }
1162
+ if (k === XK_TAB && this._tabEscapes) {
1163
+ this._tabEscapes = false;
1164
+ return; // this one belongs to the focus cycle
1165
+ }
1166
+ this._tabEscapes = false;
1167
+ if (this.handleKeyDown(ev)) ev.preventDefault?.();
1168
+ }
1169
+
1170
+ /**
1171
+ * The editor's key behaviour as a predicate — true when the key was
1172
+ * consumed. Kept apart from {@link defaultKeyDown} because the editing
1173
+ * model is also driven directly in tests and by an app that wires the raw
1174
+ * element itself; the default action is this plus the Tab/Escape bargain.
1175
+ */
1176
+ handleKeyDown(ev: EditorKeyEvent): boolean {
1177
+ this._syncProps();
1178
+ const previousNative = this._keyNative;
1179
+ this._keyNative = ev.nativeEvent ?? null;
1180
+ try {
1181
+ return this._editKey(ev);
1182
+ } finally {
1183
+ this._keyNative = previousNative;
1184
+ }
1185
+ }
1186
+
1187
+ private _editKey(ev: EditorKeyEvent): boolean {
1188
+ const k = ev.keysym;
1189
+ const readOnly = Boolean(this.props.readOnly);
1190
+ const [a, b] = this._selectionRange();
1191
+ const hasSelection = this._hasSelection();
1192
+ const wordChars = this.language?.data?.wordChars ?? '';
1193
+
1194
+ if (k === XK_RETURN || k === XK_KP_ENTER) {
1195
+ if (ev.ctrlKey) {
1196
+ this._fire('onSubmit', this.value);
1197
+ return true;
1198
+ }
1199
+ if (readOnly) return false;
1200
+ const lineText = this._lines[a.line];
1201
+ const indentMatch = /^[\t ]*/.exec(lineText)?.[0] ?? '';
1202
+ const indent = indentMatch.slice(0, Math.min(indentMatch.length, a.ch));
1203
+ const before = lineText.slice(0, a.ch);
1204
+ const rule = this.language?.data?.indentAfter;
1205
+ const deeper = rule ? rule.test(before) : false;
1206
+ const after = this._lines[b.line].slice(b.ch);
1207
+ if (deeper && /^[)\]}]/.test(after)) {
1208
+ // the caret sits between a fresh pair — `{|}` — so Enter opens it
1209
+ // the way every editor does: an indented line for the caret, the
1210
+ // closer on its own line at the old depth
1211
+ const unit = this._indentUnit();
1212
+ this.replaceRange(a, b, '\n' + indent + unit + '\n' + indent);
1213
+ this.moveCaret(
1214
+ { line: a.line + 1, ch: indent.length + unit.length },
1215
+ false,
1216
+ );
1217
+ return true;
1218
+ }
1219
+ this.insertText('\n' + indent + (deeper ? this._indentUnit() : ''));
1220
+ return true;
1221
+ }
1222
+ if (k === XK_BACKSPACE) {
1223
+ if (readOnly) return true;
1224
+ if (hasSelection) this.replaceRange(a, b, '');
1225
+ else if (ev.ctrlKey) {
1226
+ this.replaceRange(wordBoundary(this._lines, a, -1, wordChars), a, '');
1227
+ } else {
1228
+ const from = movePos(this._lines, a, -1);
1229
+ if (!posEqual(from, a)) this.replaceRange(from, a, '', 'delete-back');
1230
+ }
1231
+ return true;
1232
+ }
1233
+ if (k === XK_DELETE) {
1234
+ if (readOnly) return true;
1235
+ if (hasSelection) this.replaceRange(a, b, '');
1236
+ else if (ev.ctrlKey) {
1237
+ this.replaceRange(a, wordBoundary(this._lines, a, 1, wordChars), '');
1238
+ } else {
1239
+ const to = movePos(this._lines, a, 1);
1240
+ if (!posEqual(to, a)) this.replaceRange(a, to, '', 'delete-forward');
1241
+ }
1242
+ return true;
1243
+ }
1244
+ if (k === XK_LEFT || k === XK_RIGHT) {
1245
+ const dir = k === XK_LEFT ? -1 : 1;
1246
+ let target: Position;
1247
+ if (ev.ctrlKey) {
1248
+ target = wordBoundary(this._lines, this._caret, dir, wordChars);
1249
+ } else if (!ev.shiftKey && hasSelection) {
1250
+ target = dir < 0 ? a : b;
1251
+ } else {
1252
+ target = movePos(this._lines, this._caret, dir);
1253
+ }
1254
+ this.moveCaret(target, ev.shiftKey);
1255
+ if (ev.shiftKey) this._ownPrimary();
1256
+ return true;
1257
+ }
1258
+ if (k === XK_UP || k === XK_DOWN) {
1259
+ this._verticalMove(k === XK_UP ? -1 : 1, ev.shiftKey);
1260
+ return true;
1261
+ }
1262
+ if (k === XK_PAGE_UP || k === XK_PAGE_DOWN) {
1263
+ const content = this._contentRect();
1264
+ const page = Math.max(
1265
+ 1,
1266
+ Math.floor(content.height / this._lineHeight()) - 1,
1267
+ );
1268
+ this._verticalMove((k === XK_PAGE_UP ? -1 : 1) * page, ev.shiftKey);
1269
+ return true;
1270
+ }
1271
+ if (k === XK_HOME) {
1272
+ if (ev.ctrlKey) {
1273
+ this.moveCaret({ line: 0, ch: 0 }, ev.shiftKey);
1274
+ return true;
1275
+ }
1276
+ const text = this._lines[this._caret.line];
1277
+ const first = text.length - text.trimStart().length;
1278
+ const ch = this._caret.ch === first ? 0 : first;
1279
+ this.moveCaret({ line: this._caret.line, ch }, ev.shiftKey);
1280
+ return true;
1281
+ }
1282
+ if (k === XK_END) {
1283
+ if (ev.ctrlKey) {
1284
+ const last = this._lines.length - 1;
1285
+ this.moveCaret(
1286
+ { line: last, ch: this._lines[last].length },
1287
+ ev.shiftKey,
1288
+ );
1289
+ return true;
1290
+ }
1291
+ this.moveCaret(
1292
+ { line: this._caret.line, ch: this._lines[this._caret.line].length },
1293
+ ev.shiftKey,
1294
+ );
1295
+ return true;
1296
+ }
1297
+ if (k === XK_TAB) {
1298
+ if (readOnly) return false;
1299
+ if (ev.shiftKey) {
1300
+ this.indentSelection(-1);
1301
+ return true;
1302
+ }
1303
+ if (a.line !== b.line) {
1304
+ this.indentSelection(1);
1305
+ return true;
1306
+ }
1307
+ if (this.props.insertSpaces === false) {
1308
+ this.insertText('\t', 'type');
1309
+ return true;
1310
+ }
1311
+ // spaces to the next tab stop, measured in display columns
1312
+ const map = tabMap(this._lines[a.line].slice(0, a.ch), this._tabSize());
1313
+ const col = map.display.length;
1314
+ const size = this._tabSize();
1315
+ this.insertText(' '.repeat(size - (col % size)), 'type');
1316
+ return true;
1317
+ }
1318
+ if (k === XK_ESCAPE) {
1319
+ return false; // the component owns Escape (completion, then Tab-out)
1320
+ }
1321
+
1322
+ if (ev.ctrlKey) {
1323
+ const letter = ctrlChordLetter(ev);
1324
+ if (letter === 0x61 /* a */) {
1325
+ this.selectAll();
1326
+ return true;
1327
+ }
1328
+ if (letter === 0x63 /* c */) {
1329
+ this.copySelection();
1330
+ return true;
1331
+ }
1332
+ if (letter === 0x78 /* x */) {
1333
+ this.copySelection();
1334
+ if (!readOnly && hasSelection) this.replaceRange(a, b, '');
1335
+ return true;
1336
+ }
1337
+ if (letter === 0x76 /* v */) {
1338
+ if (!readOnly) this.pasteFrom();
1339
+ return true;
1340
+ }
1341
+ if (letter === 0x7a /* z */) {
1342
+ if (!readOnly) {
1343
+ if (ev.shiftKey) this.redo();
1344
+ else this.undo();
1345
+ }
1346
+ return true;
1347
+ }
1348
+ if (letter === 0x79 /* y */) {
1349
+ if (!readOnly) this.redo();
1350
+ return true;
1351
+ }
1352
+ if (letter === 0x2f /* / */) {
1353
+ this.toggleLineComment();
1354
+ return true;
1355
+ }
1356
+ return false;
1357
+ }
1358
+
1359
+ if (
1360
+ !readOnly &&
1361
+ ev.codepoint != null &&
1362
+ ev.codepoint >= 0x20 &&
1363
+ ev.codepoint !== 0x7f
1364
+ ) {
1365
+ const ch = String.fromCodePoint(ev.codepoint);
1366
+ this.insertText(ch, 'type');
1367
+ if (/\s/.test(ch)) this.breakUndoRun();
1368
+ return true;
1369
+ }
1370
+ return false;
1371
+ }
1372
+
1373
+ private _verticalMove(deltaLines: number, extend: boolean): void {
1374
+ if (this._goalX == null) this._goalX = this._caretX(this._caret);
1375
+ const line = Math.max(
1376
+ 0,
1377
+ Math.min(this._lines.length - 1, this._caret.line + deltaLines),
1378
+ );
1379
+ if (line === this._caret.line) {
1380
+ // pressing against the document's edge: land on its corner, the way
1381
+ // every editor does
1382
+ const ch = deltaLines < 0 ? 0 : this._lines[line].length;
1383
+ this.moveCaret({ line, ch }, extend);
1384
+ if (extend) this._ownPrimary();
1385
+ return;
1386
+ }
1387
+ const entry = this._lineEntry(line);
1388
+ let ch = 0;
1389
+ if (entry.layout) {
1390
+ const cp = entry.layout.indexAt(this._goalX, 0);
1391
+ ch = entry.map.toRaw(cpToUtf16(entry.display, cp));
1392
+ }
1393
+ const goal = this._goalX; // moveCaret clears it; this move keeps it
1394
+ this.moveCaret({ line, ch }, extend);
1395
+ this._goalX = goal;
1396
+ if (extend) this._ownPrimary();
1397
+ }
1398
+
1399
+ // --- input: mouse --------------------------------------------------------
1400
+
1401
+ /**
1402
+ * A press: place the caret, start a drag-selection, or paste PRIMARY on
1403
+ * the middle button. The drag and release below are the continuation of
1404
+ * *this* press — core vetoes a gesture once, at its press, so an editor
1405
+ * whose `defaultMouseDown` never ran hears neither, and needs no
1406
+ * `dragging` flag to discover that.
1407
+ */
1408
+ override defaultMouseDown(ev: EditorMouseEvent): void {
1409
+ if (this.props.disabled) return;
1410
+ this.focus();
1411
+ if (this.handleMouseDown(ev)) ev.preventDefault?.();
1412
+ }
1413
+
1414
+ override defaultMouseDrag(ev: EditorMouseEvent): void {
1415
+ if (this.handleMouseMove(ev)) ev.preventDefault?.();
1416
+ }
1417
+
1418
+ override defaultMouseUp(): void {
1419
+ this.handleMouseUp();
1420
+ }
1421
+
1422
+ handleMouseDown(ev: EditorMouseEvent): boolean {
1423
+ this._syncProps();
1424
+ this.breakUndoRun();
1425
+ const pos = this._posAt(ev.x, ev.y);
1426
+ if (ev.button === 3) {
1427
+ const [a, b] = this._selectionRange();
1428
+ if (comparePos(pos, a) < 0 || comparePos(pos, b) > 0) {
1429
+ this.moveCaret(pos, false);
1430
+ }
1431
+ return true;
1432
+ }
1433
+ if (ev.button === 2) {
1434
+ this.moveCaret(pos, false);
1435
+ if (!this.props.readOnly) this.pasteFrom('PRIMARY');
1436
+ return true;
1437
+ }
1438
+ const detail = ev.detail ?? 1;
1439
+ if (detail >= 3) {
1440
+ this._drag = 'line';
1441
+ this._dragOrigin = pos;
1442
+ this._selectLineRange(pos, pos);
1443
+ } else if (detail === 2) {
1444
+ this._drag = 'word';
1445
+ this._dragOrigin = pos;
1446
+ this._selectWordRange(pos, pos);
1447
+ } else {
1448
+ this._drag = 'char';
1449
+ this._dragOrigin = pos;
1450
+ this.moveCaret(pos, ev.shiftKey);
1451
+ }
1452
+ ev.capturePointer?.();
1453
+ return true;
1454
+ }
1455
+
1456
+ handleMouseMove(ev: EditorMouseEvent): boolean {
1457
+ if (!this._drag) return false;
1458
+ const pos = this._posAt(ev.x, ev.y);
1459
+ if (this._drag === 'char') this.moveCaret(pos, true);
1460
+ else if (this._drag === 'word')
1461
+ this._selectWordRange(this._dragOrigin, pos);
1462
+ else this._selectLineRange(this._dragOrigin, pos);
1463
+ return true;
1464
+ }
1465
+
1466
+ handleMouseUp(): boolean {
1467
+ if (!this._drag) return false;
1468
+ this._drag = null;
1469
+ this._ownPrimary();
1470
+ return true;
1471
+ }
1472
+
1473
+ private _selectWordRange(origin: Position, to: Position): void {
1474
+ const extra = this.language?.data?.wordChars ?? '';
1475
+ const w1 = wordRangeAt(this._lines[origin.line], origin.ch, extra);
1476
+ const w2 = wordRangeAt(this._lines[to.line], to.ch, extra);
1477
+ const start = { line: origin.line, ch: w1.from };
1478
+ const end = { line: to.line, ch: w2.to };
1479
+ if (comparePos(to, origin) < 0) {
1480
+ this.select(
1481
+ { line: origin.line, ch: w1.to },
1482
+ { line: to.line, ch: w2.from },
1483
+ );
1484
+ } else {
1485
+ this.select(start, end);
1486
+ }
1487
+ }
1488
+
1489
+ private _selectLineRange(origin: Position, to: Position): void {
1490
+ const [first, last] =
1491
+ to.line < origin.line ? [to.line, origin.line] : [origin.line, to.line];
1492
+ if (last + 1 < this._lines.length) {
1493
+ this.select({ line: first, ch: 0 }, { line: last + 1, ch: 0 });
1494
+ } else {
1495
+ this.select(
1496
+ { line: first, ch: 0 },
1497
+ { line: last, ch: this._lines[last].length },
1498
+ );
1499
+ }
1500
+ }
1501
+
1502
+ // --- focus ---------------------------------------------------------------
1503
+
1504
+ // `focus()`/`blur()` come from `Node` — declared as well as implemented
1505
+ // since react-x11#267, so the forwarding shims this file used to carry are
1506
+ // gone. `CodeEditorHandle` keeps promising `void`: the base returns `this`,
1507
+ // and widening the handle to the node type would drag the class's nominal
1508
+ // type into the exported props (see the note on `CodeEditorHandle`).
1509
+
1510
+ /** Focus is a state, not an event: `onFocus` is where the app hears about
1511
+ * it, and this is where the caret starts blinking. The timer is also
1512
+ * released in `destroySubtree`, because a node that unmounts while focused
1513
+ * is forgotten rather than blurred and this would never run. */
1514
+ override defaultFocus(): void {
1515
+ this.handleFocus();
1516
+ }
1517
+
1518
+ override defaultBlur(): void {
1519
+ this.handleBlur();
1520
+ }
1521
+
1522
+ handleFocus(): void {
1523
+ this._focused = true;
1524
+ this._caretOn = true;
1525
+ if (this._blinkTimer == null) {
1526
+ this._blinkTimer = startInterval(() => {
1527
+ this._caretOn = !this._caretOn;
1528
+ this.root?.invalidate(false, this.abs, 'text');
1529
+ }, CARET_BLINK_MS);
1530
+ }
1531
+ this._repaint();
1532
+ }
1533
+
1534
+ handleBlur(): void {
1535
+ this._focused = false;
1536
+ this.breakUndoRun();
1537
+ stopInterval(this._blinkTimer);
1538
+ this._blinkTimer = null;
1539
+ this._repaint();
1540
+ }
1541
+
1542
+ // --- lifecycle -----------------------------------------------------------
1543
+
1544
+ override applyProps(
1545
+ nextProps: Record<string, unknown>,
1546
+ prevProps: Record<string, unknown>,
1547
+ ): void {
1548
+ const before = prevProps ?? this.props;
1549
+ super.applyProps(nextProps, prevProps);
1550
+ if (nextProps.value != null) this._syncProps();
1551
+ if (nextProps.language !== before.language) {
1552
+ this._tokenizer(); // re-resolves against the new props
1553
+ this._repaint();
1554
+ }
1555
+ if (nextProps.rows !== before.rows) this.invalidateMeasure('props');
1556
+ if (
1557
+ nextProps.tokenStyles !== before.tokenStyles ||
1558
+ nextProps.diagnostics !== before.diagnostics ||
1559
+ nextProps.tabSize !== before.tabSize ||
1560
+ nextProps.lineNumbers !== before.lineNumbers
1561
+ ) {
1562
+ this._lineCache.clear();
1563
+ this._repaint();
1564
+ }
1565
+ }
1566
+
1567
+ override destroySubtree(): void {
1568
+ stopInterval(this._blinkTimer);
1569
+ this._blinkTimer = null;
1570
+ this._tok?.dispose?.();
1571
+ this._tok = null;
1572
+ super.destroySubtree();
1573
+ }
1574
+
1575
+ // --- painting ------------------------------------------------------------
1576
+
1577
+ override paint(ctx: Context2D): void {
1578
+ super.paint(ctx);
1579
+ this._syncProps();
1580
+ this._metrics();
1581
+ const c = ctx as {
1582
+ fillStyle?: unknown;
1583
+ fillRect?(x: number, y: number, w: number, h: number): void;
1584
+ save?(): void;
1585
+ restore?(): void;
1586
+ beginPath?(): void;
1587
+ rect?(x: number, y: number, w: number, h: number): void;
1588
+ clip?(): void;
1589
+ moveTo?(x: number, y: number): void;
1590
+ lineTo?(x: number, y: number): void;
1591
+ stroke?(): void;
1592
+ strokeStyle?: unknown;
1593
+ lineWidth?: number;
1594
+ };
1595
+ if (typeof c.fillRect !== 'function') return; // mock backend: geometry only
1596
+ const content = this._contentRect();
1597
+ if (content.width <= 0 || content.height <= 0) return;
1598
+ const lineH = this._lineH;
1599
+ const gutterW = this._gutterWidth();
1600
+ const textX = content.x + gutterW - this._scrollX;
1601
+ const props = this.props as unknown as CodeEditorProps;
1602
+ const styles = this._textStyle();
1603
+ const baseColor = styles.color as string;
1604
+
1605
+ const canClip =
1606
+ typeof c.save === 'function' &&
1607
+ typeof c.beginPath === 'function' &&
1608
+ typeof c.rect === 'function' &&
1609
+ typeof c.clip === 'function';
1610
+ const clipTo = (x: number, y: number, w: number, h: number): boolean => {
1611
+ if (!canClip) return false;
1612
+ c.save!();
1613
+ c.beginPath!();
1614
+ c.rect!(x, y, w, h);
1615
+ c.clip!();
1616
+ return true;
1617
+ };
1618
+ const dark = this._onDark();
1619
+
1620
+ const first = Math.max(0, Math.floor(this._scrollY / lineH));
1621
+ const last = Math.min(
1622
+ this._lines.length - 1,
1623
+ Math.ceil((this._scrollY + content.height) / lineH),
1624
+ );
1625
+ const lineY = (i: number): number => content.y + i * lineH - this._scrollY;
1626
+
1627
+ // Everything the editor draws — gutter included — stays inside the
1628
+ // content box. The first and last visible rows are partial when
1629
+ // scrolled, and without this their overhang (up to a line each way)
1630
+ // walks into the padding and past it.
1631
+ const outerClip = clipTo(
1632
+ content.x,
1633
+ content.y,
1634
+ content.width,
1635
+ content.height,
1636
+ );
1637
+
1638
+ // gutter background
1639
+ if (gutterW > 0 && props.gutterBackground) {
1640
+ c.fillStyle = this._resolveColor(props.gutterBackground) ?? '#00000008';
1641
+ c.fillRect(content.x, content.y, gutterW, content.height);
1642
+ }
1643
+
1644
+ // the text region clips one step narrower, so a horizontally scrolled
1645
+ // line cannot run underneath the line numbers
1646
+ const textClip = clipTo(
1647
+ content.x + gutterW,
1648
+ content.y,
1649
+ content.width - gutterW,
1650
+ content.height,
1651
+ );
1652
+
1653
+ // active line
1654
+ if (
1655
+ props.activeLine &&
1656
+ this._focused &&
1657
+ !this._hasSelection() &&
1658
+ this._caret.line >= first &&
1659
+ this._caret.line <= last
1660
+ ) {
1661
+ c.fillStyle =
1662
+ this._resolveColor(props.activeLineColor) ??
1663
+ (dark ? 'rgba(255,255,255,0.06)' : 'rgba(120,140,180,0.10)');
1664
+ c.fillRect(
1665
+ content.x + gutterW,
1666
+ lineY(this._caret.line),
1667
+ content.width - gutterW,
1668
+ lineH,
1669
+ );
1670
+ }
1671
+
1672
+ // selection bands
1673
+ if (this._hasSelection()) {
1674
+ const [a, b] = this._selectionRange();
1675
+ c.fillStyle =
1676
+ this._resolveColor(props.selectionColor) ??
1677
+ (dark ? 'rgba(82,139,255,0.35)' : 'rgba(80,140,255,0.28)');
1678
+ for (let i = Math.max(a.line, first); i <= Math.min(b.line, last); i++) {
1679
+ const entry = this._lineEntry(i);
1680
+ const startX =
1681
+ i === a.line && entry.layout
1682
+ ? this._caretX({ line: i, ch: a.ch })
1683
+ : 0;
1684
+ const endX =
1685
+ i === b.line
1686
+ ? this._caretX({ line: i, ch: b.ch })
1687
+ : (entry.layout?.width ?? 0) + this._charW * 0.5; // the newline
1688
+ c.fillRect(textX + startX, lineY(i), Math.max(2, endX - startX), lineH);
1689
+ }
1690
+ }
1691
+
1692
+ // matching brackets
1693
+ if (props.matchBrackets !== false && this._focused) {
1694
+ const match = this._findBracketMatch();
1695
+ if (match) {
1696
+ c.fillStyle =
1697
+ this._resolveColor(props.matchingBracketColor) ??
1698
+ (dark ? 'rgba(90,190,130,0.35)' : 'rgba(80,180,120,0.30)');
1699
+ for (const pos of match) {
1700
+ if (pos.line < first || pos.line > last) continue;
1701
+ const x0 = this._caretX(pos);
1702
+ const x1 = this._caretX({ line: pos.line, ch: pos.ch + 1 });
1703
+ c.fillRect(textX + x0, lineY(pos.line), Math.max(1, x1 - x0), lineH);
1704
+ }
1705
+ }
1706
+ }
1707
+
1708
+ // text
1709
+ const empty =
1710
+ this._lines.length === 1 &&
1711
+ this._lines[0].length === 0 &&
1712
+ props.placeholder;
1713
+ if (empty) {
1714
+ const fonts = this._fonts();
1715
+ if (fonts) {
1716
+ const layout = fonts.layout(
1717
+ [
1718
+ {
1719
+ text: String(props.placeholder),
1720
+ color:
1721
+ this._resolveColor(props.placeholderColor) ??
1722
+ this._themeColor('dim') ??
1723
+ '#9aa0a6',
1724
+ },
1725
+ ],
1726
+ styles,
1727
+ );
1728
+ layout.draw(ctx, textX, lineY(0) + (lineH - layout.height) / 2);
1729
+ }
1730
+ } else {
1731
+ for (let i = first; i <= last; i++) {
1732
+ const entry = this._lineEntry(i);
1733
+ entry.layout?.draw(
1734
+ ctx,
1735
+ textX,
1736
+ lineY(i) + (lineH - (entry.layout.height || lineH)) / 2,
1737
+ );
1738
+ }
1739
+ }
1740
+
1741
+ // diagnostics
1742
+ const diagnostics = props.diagnostics ?? [];
1743
+ for (const d of diagnostics) {
1744
+ const color = SEVERITY_COLORS[d.severity ?? 'error'] ?? '#e5484d';
1745
+ const from = clampPos(this._lines, d.from);
1746
+ const to = clampPos(this._lines, maxPos(d.from, d.to));
1747
+ for (
1748
+ let i = Math.max(from.line, first);
1749
+ i <= Math.min(to.line, last);
1750
+ i++
1751
+ ) {
1752
+ const x0 = i === from.line ? this._caretX({ line: i, ch: from.ch }) : 0;
1753
+ const lineText = this._lines[i];
1754
+ const x1 =
1755
+ i === to.line
1756
+ ? this._caretX({ line: i, ch: to.ch })
1757
+ : this._caretX({ line: i, ch: lineText.length });
1758
+ // hug the glyphs, not the line box: the text is centred in its
1759
+ // band, so a squiggle at the band's bottom floats visibly below
1760
+ // short text — measure where the drawn layout actually ends
1761
+ const entry = this._lineEntry(i);
1762
+ const glyphBottom =
1763
+ lineY(i) +
1764
+ (lineH - (entry.layout?.height || lineH)) / 2 +
1765
+ (entry.layout?.height || lineH);
1766
+ const y = Math.min(lineY(i) + lineH - 2, glyphBottom - 1);
1767
+ const width = Math.max(this._charW * 0.6, x1 - x0);
1768
+ if (
1769
+ typeof c.beginPath === 'function' &&
1770
+ typeof c.moveTo === 'function' &&
1771
+ typeof c.lineTo === 'function' &&
1772
+ typeof c.stroke === 'function'
1773
+ ) {
1774
+ c.beginPath();
1775
+ const step = 4;
1776
+ let up = true;
1777
+ for (let x = 0; x <= width; x += step) {
1778
+ const px = textX + x0 + x;
1779
+ const py = y + (up ? 0 : 2);
1780
+ if (x === 0) c.moveTo(px, py);
1781
+ else c.lineTo(px, py);
1782
+ up = !up;
1783
+ }
1784
+ c.strokeStyle = color;
1785
+ c.lineWidth = 1;
1786
+ c.stroke();
1787
+ } else {
1788
+ c.fillStyle = color;
1789
+ c.fillRect(textX + x0, y + 1, width, 1);
1790
+ }
1791
+ }
1792
+ }
1793
+
1794
+ // caret — 2px, in the (theme-resolved) text colour
1795
+ if (this._focused && this._caretOn) {
1796
+ const x = this._caretX(this._caret);
1797
+ c.fillStyle = this._resolveColor(props.caretColor) ?? baseColor;
1798
+ c.fillRect(
1799
+ Math.round(textX + x),
1800
+ lineY(this._caret.line) + 1,
1801
+ 2,
1802
+ lineH - 2,
1803
+ );
1804
+ }
1805
+
1806
+ if (textClip) c.restore!();
1807
+
1808
+ // gutter numbers: inside the content clip, outside the text clip
1809
+ if (gutterW > 0) {
1810
+ const fonts = this._fonts();
1811
+ if (fonts) {
1812
+ const dim =
1813
+ this._resolveColor(props.gutterColor) ??
1814
+ this._themeColor('dim') ??
1815
+ '#9aa0a6';
1816
+ for (let i = first; i <= last; i++) {
1817
+ const active = i === this._caret.line;
1818
+ const layout = fonts.layout(
1819
+ [
1820
+ {
1821
+ text: String(i + 1),
1822
+ color: active ? baseColor : dim,
1823
+ },
1824
+ ],
1825
+ styles,
1826
+ );
1827
+ layout.draw(
1828
+ ctx,
1829
+ content.x + gutterW - GUTTER_PAD - layout.width,
1830
+ lineY(i) + (lineH - layout.height) / 2,
1831
+ );
1832
+ }
1833
+ }
1834
+ }
1835
+
1836
+ // scroll thumbs — minimal, panel-style
1837
+ const maxY = this._maxScrollY();
1838
+ if (maxY > 0) {
1839
+ const trackH = content.height;
1840
+ const thumbH = Math.max(
1841
+ 24,
1842
+ (trackH * trackH) / (this._lines.length * lineH),
1843
+ );
1844
+ const yOff = (this._scrollY / maxY) * (trackH - thumbH);
1845
+ c.fillStyle = 'rgba(128,128,128,0.35)';
1846
+ c.fillRect(content.x + content.width - 4, content.y + yOff, 3, thumbH);
1847
+ }
1848
+ const maxX = this._maxScrollX();
1849
+ if (maxX > 0) {
1850
+ const textW = content.width - gutterW;
1851
+ const trackW = textW;
1852
+ const thumbW = Math.max(
1853
+ 24,
1854
+ (trackW * trackW) / (this._widest + CARET_MARGIN),
1855
+ );
1856
+ const xOff = (this._scrollX / maxX) * (trackW - thumbW);
1857
+ c.fillStyle = 'rgba(128,128,128,0.35)';
1858
+ c.fillRect(
1859
+ content.x + gutterW + xOff,
1860
+ content.y + content.height - 4,
1861
+ thumbW,
1862
+ 3,
1863
+ );
1864
+ }
1865
+
1866
+ if (outerClip) c.restore!();
1867
+ }
1868
+
1869
+ /** The bracket pair to highlight: the caret sits beside one of them. */
1870
+ private _findBracketMatch(): [Position, Position] | null {
1871
+ const pos = this._caret;
1872
+ const line = this._lines[pos.line];
1873
+ const before = pos.ch > 0 ? line.charAt(pos.ch - 1) : '';
1874
+ const after = pos.ch < line.length ? line.charAt(pos.ch) : '';
1875
+ let at: Position;
1876
+ let ch: string;
1877
+ if (after in BRACKETS) {
1878
+ at = { line: pos.line, ch: pos.ch };
1879
+ ch = after;
1880
+ } else if (before in BRACKETS) {
1881
+ at = { line: pos.line, ch: pos.ch - 1 };
1882
+ ch = before;
1883
+ } else {
1884
+ return null;
1885
+ }
1886
+ if (this._isOpaqueAt(at)) return null;
1887
+ const { match, dir } = BRACKETS[ch];
1888
+ let depth = 0;
1889
+ let scanned = 0;
1890
+ let l = at.line;
1891
+ let i = at.ch;
1892
+ while (l >= 0 && l < this._lines.length && scanned < MATCH_SCAN_LIMIT) {
1893
+ const text = this._lines[l];
1894
+ while (dir > 0 ? i < text.length : i >= 0) {
1895
+ const cur = text.charAt(i);
1896
+ if (
1897
+ (cur === ch || cur === match) &&
1898
+ !this._isOpaqueAt({ line: l, ch: i })
1899
+ ) {
1900
+ if (cur === ch) depth++;
1901
+ else {
1902
+ depth--;
1903
+ if (depth === 0) return [at, { line: l, ch: i }];
1904
+ }
1905
+ }
1906
+ i += dir;
1907
+ scanned++;
1908
+ }
1909
+ l += dir;
1910
+ if (l >= 0 && l < this._lines.length) {
1911
+ i = dir > 0 ? 0 : this._lines[l].length - 1;
1912
+ }
1913
+ }
1914
+ return null;
1915
+ }
1916
+
1917
+ private _isOpaqueAt(pos: Position): boolean {
1918
+ const tokens = this._tokenizer()?.lineTokens(pos.line);
1919
+ if (!tokens) return false;
1920
+ for (const t of tokens) {
1921
+ if (t.from <= pos.ch && pos.ch < t.to) return OPAQUE_TOKENS.has(t.type);
1922
+ if (t.from > pos.ch) break;
1923
+ }
1924
+ return false;
1925
+ }
1926
+ }