silvery 0.21.0 → 0.23.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 (224) hide show
  1. package/README.md +1 -1
  2. package/dist/{Text-Lq0dmj8-.mjs → Text-Ci6UCSTL.mjs} +46 -48
  3. package/dist/Text-Ci6UCSTL.mjs.map +1 -0
  4. package/dist/{render-string-DkQacASz.mjs → ag-q9lRlKlt.mjs} +5322 -4259
  5. package/dist/ag-q9lRlKlt.mjs.map +1 -0
  6. package/dist/{animation-ZMN2_XKv.mjs → animation-CZ4x2R7C.mjs} +1 -1
  7. package/dist/{animation-ZMN2_XKv.mjs.map → animation-CZ4x2R7C.mjs.map} +1 -1
  8. package/dist/{ansi-D1KQMAbf.mjs → ansi-CLmIMnop.mjs} +1 -1
  9. package/dist/{ansi-D1KQMAbf.mjs.map → ansi-CLmIMnop.mjs.map} +1 -1
  10. package/dist/{ansi-2Xn0yatP.d.mts → ansi-zmNzgkPB.d.mts} +1 -1
  11. package/dist/{ansi-2Xn0yatP.d.mts.map → ansi-zmNzgkPB.d.mts.map} +1 -1
  12. package/dist/{backend-B-WYLUib.mjs → backend-DSiSIc-h.mjs} +2 -2
  13. package/dist/{backend-B-WYLUib.mjs.map → backend-DSiSIc-h.mjs.map} +1 -1
  14. package/dist/chain-bridge-CkYcMxlq.mjs +4149 -0
  15. package/dist/chain-bridge-CkYcMxlq.mjs.map +1 -0
  16. package/dist/{chunk-BSw8zbkd.mjs → chunk-BEJ448es.mjs} +1 -3
  17. package/dist/cli-VNqUKlXw.mjs +4 -0
  18. package/dist/{context-BU5LkkIy.mjs → context-CEhuD1R_.mjs} +1 -1
  19. package/dist/{context-BU5LkkIy.mjs.map → context-CEhuD1R_.mjs.map} +1 -1
  20. package/dist/devtools-BF4IMumr.mjs +2 -0
  21. package/dist/{devtools-DcQjgyjL.mjs → devtools-td2AjFwc.mjs} +5 -5
  22. package/dist/{devtools-DcQjgyjL.mjs.map → devtools-td2AjFwc.mjs.map} +1 -1
  23. package/dist/{easing-BI-ASGMO.d.mts → easing-C5MrdO7V.d.mts} +1 -1
  24. package/dist/{easing-BI-ASGMO.d.mts.map → easing-C5MrdO7V.d.mts.map} +1 -1
  25. package/dist/{eta-CJlGH06n.mjs → eta-BwYWEjfS.mjs} +1 -1
  26. package/dist/{eta-CJlGH06n.mjs.map → eta-BwYWEjfS.mjs.map} +1 -1
  27. package/dist/{flexily-zero-adapter-C4lW_Ov5.mjs → flexily-zero-adapter-DktX3HsX.mjs} +1 -1
  28. package/dist/{flexily-zero-adapter-C3Vj0fPt.mjs → flexily-zero-adapter-VoBGHG14.mjs} +1 -1
  29. package/dist/{flexily-zero-adapter-C3Vj0fPt.mjs.map → flexily-zero-adapter-VoBGHG14.mjs.map} +1 -1
  30. package/dist/hit-registry-D_nxOivS.d.mts +71 -0
  31. package/dist/hit-registry-D_nxOivS.d.mts.map +1 -0
  32. package/dist/{UPNG-DosRPdF4.mjs → image-CIF75xMr.mjs} +891 -6
  33. package/dist/image-CIF75xMr.mjs.map +1 -0
  34. package/dist/{index-CSQf13CI.d.mts → index-377zdPLP.d.mts} +35 -12
  35. package/dist/index-377zdPLP.d.mts.map +1 -0
  36. package/dist/{index-Cl9KKjQ_.d.mts → index-BcSr0RAQ.d.mts} +8670 -10659
  37. package/dist/index-BcSr0RAQ.d.mts.map +1 -0
  38. package/dist/{index-XbNrPhWl.d.mts → index-DLnpxDAm.d.mts} +2 -2
  39. package/dist/{index-XbNrPhWl.d.mts.map → index-DLnpxDAm.d.mts.map} +1 -1
  40. package/dist/{index-BUMxS65f.d.mts → index-DlNmckTq.d.mts} +2 -2
  41. package/dist/{index-BUMxS65f.d.mts.map → index-DlNmckTq.d.mts.map} +1 -1
  42. package/dist/index.d.mts +12 -8
  43. package/dist/index.d.mts.map +1 -1
  44. package/dist/index.mjs +18 -15
  45. package/dist/index.mjs.map +1 -1
  46. package/dist/{layout-engine-C2px0RJE.mjs → layout-engine-BgR58vL0.mjs} +3 -3
  47. package/dist/{layout-engine-C2px0RJE.mjs.map → layout-engine-BgR58vL0.mjs.map} +1 -1
  48. package/dist/{layout-engine-C07LEXWT.mjs → layout-engine-DNjTha8f.mjs} +1 -1
  49. package/dist/{layout-signals-Cnw6xk8Q.mjs → layout-signals-CGYbHmxy.mjs} +146 -23
  50. package/dist/layout-signals-CGYbHmxy.mjs.map +1 -0
  51. package/dist/{bound-term-0sPrrzH1.d.mts → mouse-events-B2z5tbsL.d.mts} +780 -379
  52. package/dist/mouse-events-B2z5tbsL.d.mts.map +1 -0
  53. package/dist/{mouse-events-Dki3ISIp.mjs → mouse-events-WQ2h-uqG.mjs} +45 -10
  54. package/dist/mouse-events-WQ2h-uqG.mjs.map +1 -0
  55. package/dist/{multi-progress-DHZ2xUT2.d.mts → multi-progress-BaEFfHKP.d.mts} +2 -2
  56. package/dist/{multi-progress-DHZ2xUT2.d.mts.map → multi-progress-BaEFfHKP.d.mts.map} +1 -1
  57. package/dist/{multi-progress-CIRjrzma.mjs → multi-progress-zCWHEm9P.mjs} +3 -3
  58. package/dist/{multi-progress-CIRjrzma.mjs.map → multi-progress-zCWHEm9P.mjs.map} +1 -1
  59. package/dist/{node-CjM5Rt-M.mjs → node-CJ3J_rW-.mjs} +1 -1
  60. package/dist/{node-CjM5Rt-M.mjs.map → node-CJ3J_rW-.mjs.map} +1 -1
  61. package/dist/{progress-DB_Xo071.mjs → progress-6vJ7nxUg.mjs} +4 -4
  62. package/dist/{progress-DB_Xo071.mjs.map → progress-6vJ7nxUg.mjs.map} +1 -1
  63. package/dist/{progress-bar-oJwq22CR.mjs → progress-bar-BlFNsZL8.mjs} +4 -4
  64. package/dist/{progress-bar-oJwq22CR.mjs.map → progress-bar-BlFNsZL8.mjs.map} +1 -1
  65. package/dist/{reconciler-DldIJB93.mjs → reconciler--_-biN23.mjs} +190 -134
  66. package/dist/reconciler--_-biN23.mjs.map +1 -0
  67. package/dist/{render-string-BcoCpjCB.mjs → render-string-DaghHpLN.mjs} +1 -1
  68. package/dist/render-string-DtHZP0cU.mjs +215 -0
  69. package/dist/render-string-DtHZP0cU.mjs.map +1 -0
  70. package/dist/runtime.d.mts +4 -3
  71. package/dist/runtime.mjs +4 -3
  72. package/dist/schemes-DWQPxXAi.mjs +2516 -0
  73. package/dist/{schemes-JjNp4aSl.mjs.map → schemes-DWQPxXAi.mjs.map} +1 -1
  74. package/dist/{spinner-D9lrHr8s.mjs → spinner-B50hILqx.mjs} +19 -4
  75. package/dist/{spinner-D9lrHr8s.mjs.map → spinner-B50hILqx.mjs.map} +1 -1
  76. package/dist/{spinner-CZINHpkV.d.mts → spinner-DN10UfF0.d.mts} +2 -2
  77. package/dist/{spinner-CZINHpkV.d.mts.map → spinner-DN10UfF0.d.mts.map} +1 -1
  78. package/dist/{src-BNTToU7l.mjs → src-B8lf1kiR.mjs} +1507 -1166
  79. package/dist/src-B8lf1kiR.mjs.map +1 -0
  80. package/dist/{src-5w9QR6_8.mjs → src-BOLdi3WG.mjs} +105 -237
  81. package/dist/src-BOLdi3WG.mjs.map +1 -0
  82. package/dist/{src-BR4xNwdG.mjs → src-BXeTHHlL.mjs} +20240 -25437
  83. package/dist/src-BXeTHHlL.mjs.map +1 -0
  84. package/dist/{src-DKp-_OFG.mjs → src-BqeBFB3X.mjs} +90 -47
  85. package/dist/src-BqeBFB3X.mjs.map +1 -0
  86. package/dist/src-DbqcSrhy.mjs +3901 -0
  87. package/dist/src-DbqcSrhy.mjs.map +1 -0
  88. package/dist/{steps-Bp2uNqnn.d.mts → steps-B2kBmFpb.d.mts} +1 -1
  89. package/dist/{steps-Bp2uNqnn.d.mts.map → steps-B2kBmFpb.d.mts.map} +1 -1
  90. package/dist/{svg-Cz0UXcDj.mjs → svg-NmV9dgB9.mjs} +1 -1
  91. package/dist/{svg-Cz0UXcDj.mjs.map → svg-NmV9dgB9.mjs.map} +1 -1
  92. package/dist/{svg-g1D6ErwR.d.mts → svg-VUr78l5d.d.mts} +1 -1
  93. package/dist/{svg-g1D6ErwR.d.mts.map → svg-VUr78l5d.d.mts.map} +1 -1
  94. package/dist/term.d.mts +2 -2
  95. package/dist/term.mjs +3 -8
  96. package/dist/test.d.mts +673 -0
  97. package/dist/test.d.mts.map +1 -0
  98. package/dist/test.mjs +2829 -0
  99. package/dist/test.mjs.map +1 -0
  100. package/dist/theme.d.mts +2 -2
  101. package/dist/theme.d.mts.map +1 -1
  102. package/dist/theme.mjs +3 -8
  103. package/dist/{types-kt_fKR37.d.mts → types-D_E0FbqA.d.mts} +1 -1
  104. package/dist/{types-kt_fKR37.d.mts.map → types-D_E0FbqA.d.mts.map} +1 -1
  105. package/dist/ui/animation.d.mts +2 -2
  106. package/dist/ui/animation.mjs +1 -1
  107. package/dist/ui/ansi.d.mts +1 -1
  108. package/dist/ui/ansi.mjs +1 -1
  109. package/dist/ui/cli.d.mts +3 -3
  110. package/dist/ui/cli.mjs +5 -5
  111. package/dist/ui/image.d.mts +1 -1
  112. package/dist/ui/image.mjs +1 -1
  113. package/dist/ui/progress.d.mts +4 -4
  114. package/dist/ui/progress.mjs +4 -4
  115. package/dist/ui/react.d.mts +1 -1
  116. package/dist/ui/react.mjs +2 -2
  117. package/dist/ui/recording-chrome-react.d.mts +1 -1
  118. package/dist/ui/recording-chrome-react.mjs +2 -2
  119. package/dist/ui/recording-chrome.d.mts +1 -1
  120. package/dist/ui/recording-chrome.mjs +1 -1
  121. package/dist/ui/utils.mjs +1 -1
  122. package/dist/ui/wrappers.d.mts +2 -2
  123. package/dist/ui/wrappers.mjs +1 -1
  124. package/dist/ui.d.mts +6 -6
  125. package/dist/ui.mjs +7 -7
  126. package/dist/unicode-CxyxVGmB.mjs +11077 -0
  127. package/dist/unicode-CxyxVGmB.mjs.map +1 -0
  128. package/dist/{useLatest-DRDDVwjh.d.mts → useLatest-UOvWy0ZW.d.mts} +2 -2
  129. package/dist/{useLatest-DRDDVwjh.d.mts.map → useLatest-UOvWy0ZW.d.mts.map} +1 -1
  130. package/dist/useLayout-D5cE8OCI.mjs +424 -0
  131. package/dist/useLayout-D5cE8OCI.mjs.map +1 -0
  132. package/dist/{with-text-input-YeohVLeo.d.mts → with-text-input-DLoSCBrm.d.mts} +3 -3
  133. package/dist/{with-text-input-YeohVLeo.d.mts.map → with-text-input-DLoSCBrm.d.mts.map} +1 -1
  134. package/dist/wrap-measurer-registration-IV2HtcCd.d.mts +3106 -0
  135. package/dist/wrap-measurer-registration-IV2HtcCd.d.mts.map +1 -0
  136. package/dist/{wrapper-C70ATkVv.mjs → wrapper-CD29afOs.mjs} +59 -17
  137. package/dist/wrapper-CD29afOs.mjs.map +1 -0
  138. package/dist/{wrappers-BCUYITrY.mjs → wrappers-CInBJm44.mjs} +18 -8
  139. package/dist/wrappers-CInBJm44.mjs.map +1 -0
  140. package/dist/{yoga-adapter-BnZX1PAY.mjs → yoga-adapter-9uab5Qn-.mjs} +2 -2
  141. package/dist/{yoga-adapter-BnZX1PAY.mjs.map → yoga-adapter-9uab5Qn-.mjs.map} +1 -1
  142. package/dist/yoga-adapter-yqTl1aXK.mjs +2 -0
  143. package/package.json +59 -14
  144. package/dist/Text-Lq0dmj8-.mjs.map +0 -1
  145. package/dist/UPNG-Bo33r8rA.mjs +0 -3
  146. package/dist/UPNG-DosRPdF4.mjs.map +0 -1
  147. package/dist/__vite-browser-external-2447137e-D_JM6skp.mjs +0 -6
  148. package/dist/__vite-browser-external-2447137e-D_JM6skp.mjs.map +0 -1
  149. package/dist/ansi-yC4RyBNY.mjs +0 -22441
  150. package/dist/ansi-yC4RyBNY.mjs.map +0 -1
  151. package/dist/apng-CR08rIaH.mjs +0 -58
  152. package/dist/apng-CR08rIaH.mjs.map +0 -1
  153. package/dist/apng-DaHfVaVI.mjs +0 -3
  154. package/dist/assets/resvgjs.darwin-arm64-BtufyGW1.node +0 -0
  155. package/dist/assets/skia.darwin-arm64-DQs5sT6N.node +0 -0
  156. package/dist/backends-CUtan80W.mjs +0 -3
  157. package/dist/backends-DIVYzKqd.mjs +0 -1083
  158. package/dist/backends-DIVYzKqd.mjs.map +0 -1
  159. package/dist/bound-term-0sPrrzH1.d.mts.map +0 -1
  160. package/dist/canvas-1v7dPT-_.mjs +0 -3
  161. package/dist/canvas-CSuPOMNt.mjs +0 -1442
  162. package/dist/canvas-CSuPOMNt.mjs.map +0 -1
  163. package/dist/cli-dvo0r2fs.mjs +0 -4
  164. package/dist/compare-CQodSH4G.mjs +0 -376
  165. package/dist/compare-CQodSH4G.mjs.map +0 -1
  166. package/dist/compare-DHlcxEYA.mjs +0 -3
  167. package/dist/devtools-CJdt5H0X.mjs +0 -2
  168. package/dist/fonts-BFmhXDv7.mjs +0 -88
  169. package/dist/fonts-BFmhXDv7.mjs.map +0 -1
  170. package/dist/gif-C_AjaT9d.mjs +0 -188
  171. package/dist/gif-C_AjaT9d.mjs.map +0 -1
  172. package/dist/gif-DaC4XrxA.mjs +0 -3
  173. package/dist/gifenc-BOUT-KFB.mjs +0 -730
  174. package/dist/gifenc-BOUT-KFB.mjs.map +0 -1
  175. package/dist/image-C2Birh2x.mjs +0 -1252
  176. package/dist/image-C2Birh2x.mjs.map +0 -1
  177. package/dist/index-CSQf13CI.d.mts.map +0 -1
  178. package/dist/index-Cl9KKjQ_.d.mts.map +0 -1
  179. package/dist/key-mapping-CS-YD_cD.mjs +0 -132
  180. package/dist/key-mapping-CS-YD_cD.mjs.map +0 -1
  181. package/dist/key-mapping-Yn-Jgrij.mjs +0 -3
  182. package/dist/layout-signals-Cnw6xk8Q.mjs.map +0 -1
  183. package/dist/mouse-events-Dki3ISIp.mjs.map +0 -1
  184. package/dist/playwright-D5YiZcNS.mjs +0 -76397
  185. package/dist/playwright-D5YiZcNS.mjs.map +0 -1
  186. package/dist/png-codec-Dp84742B.mjs +0 -36
  187. package/dist/png-codec-Dp84742B.mjs.map +0 -1
  188. package/dist/png-codec-QwOtJ8Zs.mjs +0 -3
  189. package/dist/rasterizer-BRXrDdWx.mjs +0 -3
  190. package/dist/rasterizer-CpEhJvdR.mjs +0 -296
  191. package/dist/rasterizer-CpEhJvdR.mjs.map +0 -1
  192. package/dist/reconciler-DldIJB93.mjs.map +0 -1
  193. package/dist/render-string-DkQacASz.mjs.map +0 -1
  194. package/dist/resvg-js-DkOndZI3.mjs +0 -203
  195. package/dist/resvg-js-DkOndZI3.mjs.map +0 -1
  196. package/dist/schemes-JjNp4aSl.mjs +0 -2611
  197. package/dist/src-5w9QR6_8.mjs.map +0 -1
  198. package/dist/src-BNTToU7l.mjs.map +0 -1
  199. package/dist/src-BR4xNwdG.mjs.map +0 -1
  200. package/dist/src-DKp-_OFG.mjs.map +0 -1
  201. package/dist/src-bt8wSrfJ.mjs +0 -258
  202. package/dist/src-bt8wSrfJ.mjs.map +0 -1
  203. package/dist/src-e33Y6kNJ.mjs +0 -3
  204. package/dist/src-iDwu25UD.mjs +0 -1814
  205. package/dist/src-iDwu25UD.mjs.map +0 -1
  206. package/dist/svg-15lZZzxq.mjs +0 -486
  207. package/dist/svg-15lZZzxq.mjs.map +0 -1
  208. package/dist/svg-DY72a4HK.mjs +0 -3
  209. package/dist/term.mjs.map +0 -1
  210. package/dist/theme.mjs.map +0 -1
  211. package/dist/ui/display.d.mts +0 -35
  212. package/dist/ui/display.d.mts.map +0 -1
  213. package/dist/ui/display.mjs +0 -123
  214. package/dist/ui/display.mjs.map +0 -1
  215. package/dist/ui/input.d.mts +0 -184
  216. package/dist/ui/input.d.mts.map +0 -1
  217. package/dist/ui/input.mjs +0 -285
  218. package/dist/ui/input.mjs.map +0 -1
  219. package/dist/wrapper-C70ATkVv.mjs.map +0 -1
  220. package/dist/wrappers-BCUYITrY.mjs.map +0 -1
  221. package/dist/yoga-adapter-DxgsQ_gg.mjs +0 -2
  222. package/dist/zipBundle-3nqeDRtm.mjs +0 -3
  223. package/dist/zipBundle-VNAYFmqJ.mjs +0 -2003
  224. package/dist/zipBundle-VNAYFmqJ.mjs.map +0 -1
@@ -0,0 +1,3901 @@
1
+ import { C as findFocusableAncestor, D as isFocusHidden, E as getTabOrder$1, O as isFocusable, T as getExplicitFocusLink, b as setFocused, w as findSpatialTarget, x as findByTestID } from "./mouse-events-WQ2h-uqG.mjs";
2
+ import { f as syncFocusedSignal } from "./layout-signals-CGYbHmxy.mjs";
3
+ import { N as stripAnsi, Rt as queryKittyKeyboard, U as getInternalStreams, mt as createOutputPhase, r as createMeasurer } from "./unicode-CxyxVGmB.mjs";
4
+ import { j as createTerminalProfile } from "./src-B8lf1kiR.mjs";
5
+ import { C as isDirty } from "./reconciler--_-biN23.mjs";
6
+ import "./layout-engine-BgR58vL0.mjs";
7
+ import { a as setActiveColorLevel, i as parseColor } from "./ag-q9lRlKlt.mjs";
8
+ import { a as NodeContext } from "./context-CEhuD1R_.mjs";
9
+ import { u as useScrollRect } from "./useLayout-D5cE8OCI.mjs";
10
+ import { createLogger } from "loggily";
11
+ import React, { createContext, useContext, useEffect, useLayoutEffect, useRef } from "react";
12
+ //#region packages/ag/src/focus-manager.ts
13
+ function createFocusManager(options) {
14
+ const onFocusChange = options?.onFocusChange;
15
+ let activeElement = null;
16
+ let activeId = null;
17
+ let previousElement = null;
18
+ let previousId = null;
19
+ let focusOrigin = null;
20
+ const scopeStack = [];
21
+ const scopeMemory = {};
22
+ let activeScopeId = null;
23
+ const hookFocusables = [];
24
+ let hookFocusEnabled = true;
25
+ const listeners = /* @__PURE__ */ new Set();
26
+ let snapshot = null;
27
+ /** Counter incremented on every notify(); used by activateScope to detect inner notifications. */
28
+ let notifyCount = 0;
29
+ function notify() {
30
+ snapshot = null;
31
+ notifyCount++;
32
+ for (const listener of listeners) listener();
33
+ }
34
+ function getTestID(node) {
35
+ const props = node.props;
36
+ return typeof props.testID === "string" ? props.testID : null;
37
+ }
38
+ function focus(node, origin = "programmatic") {
39
+ if (activeElement === node) {
40
+ if (focusOrigin !== origin) {
41
+ focusOrigin = origin;
42
+ notify();
43
+ }
44
+ return;
45
+ }
46
+ const oldElement = activeElement;
47
+ previousElement = activeElement;
48
+ previousId = activeId;
49
+ activeElement = node;
50
+ activeId = getTestID(node);
51
+ focusOrigin = origin;
52
+ if (oldElement) {
53
+ setFocused(oldElement, false);
54
+ syncFocusedSignal(oldElement, false);
55
+ }
56
+ setFocused(node, true);
57
+ syncFocusedSignal(node, true);
58
+ if (activeId && scopeStack.length > 0) scopeMemory[scopeStack[scopeStack.length - 1]] = activeId;
59
+ notify();
60
+ onFocusChange?.(oldElement, node, origin);
61
+ }
62
+ function focusById(id, root, origin = "programmatic") {
63
+ const node = findByTestID(root, id);
64
+ if (node) {
65
+ const focusable = findFocusableAncestor(node);
66
+ if (focusable) {
67
+ focus(focusable, origin);
68
+ return;
69
+ }
70
+ }
71
+ if (activeId === id && !activeElement) return;
72
+ const oldElement = activeElement;
73
+ previousElement = activeElement;
74
+ previousId = activeId;
75
+ activeElement = null;
76
+ activeId = id;
77
+ focusOrigin = origin;
78
+ if (oldElement) {
79
+ setFocused(oldElement, false);
80
+ syncFocusedSignal(oldElement, false);
81
+ }
82
+ notify();
83
+ onFocusChange?.(oldElement, null, origin);
84
+ }
85
+ function blur() {
86
+ if (!activeElement && !activeId) return;
87
+ const oldElement = activeElement;
88
+ previousElement = activeElement;
89
+ previousId = activeId;
90
+ activeElement = null;
91
+ activeId = null;
92
+ focusOrigin = null;
93
+ if (oldElement) {
94
+ setFocused(oldElement, false);
95
+ syncFocusedSignal(oldElement, false);
96
+ }
97
+ notify();
98
+ onFocusChange?.(oldElement, null, null);
99
+ }
100
+ /**
101
+ * Set the focused target to a hook-registered id directly (no tree lookup).
102
+ * activeElement becomes null (no backing node), activeId is the hook id.
103
+ */
104
+ function focusVirtualId(id, origin = "programmatic") {
105
+ if (activeId === id && !activeElement) {
106
+ if (focusOrigin !== origin) {
107
+ focusOrigin = origin;
108
+ notify();
109
+ }
110
+ return;
111
+ }
112
+ const oldElement = activeElement;
113
+ previousElement = activeElement;
114
+ previousId = activeId;
115
+ activeElement = null;
116
+ activeId = id;
117
+ focusOrigin = origin;
118
+ if (oldElement) {
119
+ setFocused(oldElement, false);
120
+ syncFocusedSignal(oldElement, false);
121
+ }
122
+ notify();
123
+ onFocusChange?.(oldElement, null, origin);
124
+ }
125
+ function unregisterHookFocusable(id) {
126
+ const idx = hookFocusables.findIndex((f) => f.id === id);
127
+ if (idx === -1) return;
128
+ hookFocusables.splice(idx, 1);
129
+ if (activeId === id && !activeElement) {
130
+ previousId = activeId;
131
+ activeId = null;
132
+ focusOrigin = null;
133
+ notify();
134
+ onFocusChange?.(null, null, null);
135
+ return;
136
+ }
137
+ notify();
138
+ }
139
+ function registerHookFocusable(id, opts = {}) {
140
+ const { isActive = true, autoFocus = false } = opts;
141
+ const existing = hookFocusables.findIndex((f) => f.id === id);
142
+ if (existing !== -1) {
143
+ if (hookFocusables[existing].isActive === isActive && !autoFocus) return () => unregisterHookFocusable(id);
144
+ hookFocusables[existing] = {
145
+ id,
146
+ isActive
147
+ };
148
+ } else hookFocusables.push({
149
+ id,
150
+ isActive
151
+ });
152
+ if (autoFocus && isActive && activeId === null) focusVirtualId(id, "programmatic");
153
+ else notify();
154
+ return () => unregisterHookFocusable(id);
155
+ }
156
+ function setHookFocusableActive(id, isActive) {
157
+ const entry = hookFocusables.find((f) => f.id === id);
158
+ if (!entry) return;
159
+ if (entry.isActive === isActive) return;
160
+ entry.isActive = isActive;
161
+ if (!isActive && activeId === id && !activeElement) {
162
+ previousId = activeId;
163
+ activeId = null;
164
+ focusOrigin = null;
165
+ notify();
166
+ onFocusChange?.(null, null, null);
167
+ return;
168
+ }
169
+ notify();
170
+ }
171
+ function setHookFocusEnabled(enabled) {
172
+ if (hookFocusEnabled === enabled) return;
173
+ hookFocusEnabled = enabled;
174
+ notify();
175
+ }
176
+ /**
177
+ * Check if a node is the given target or contains it as a descendant.
178
+ */
179
+ function subtreeContains(subtreeRoot, target) {
180
+ if (subtreeRoot === target) return true;
181
+ for (const child of subtreeRoot.children) if (subtreeContains(child, target)) return true;
182
+ return false;
183
+ }
184
+ function updateInvalidatesFocusedNode(updatedNode, focusedNode) {
185
+ if (!subtreeContains(updatedNode, focusedNode)) return false;
186
+ let current = focusedNode;
187
+ while (current) {
188
+ if (isFocusHidden(current)) return true;
189
+ if (current === updatedNode) break;
190
+ current = current.parent;
191
+ }
192
+ return updatedNode === focusedNode && !isFocusable(focusedNode);
193
+ }
194
+ /**
195
+ * Handle a subtree being removed from the tree. If the active or previous
196
+ * element lives within the removed subtree, clear the dangling reference.
197
+ * This prevents dead node retention and broken navigation (indexOf → -1).
198
+ */
199
+ function handleSubtreeRemoved(removedRoot) {
200
+ let changed = false;
201
+ if (activeElement && subtreeContains(removedRoot, activeElement)) {
202
+ const oldElement = activeElement;
203
+ setFocused(oldElement, false);
204
+ syncFocusedSignal(oldElement, false);
205
+ previousElement = activeElement;
206
+ previousId = activeId;
207
+ activeElement = null;
208
+ activeId = null;
209
+ focusOrigin = null;
210
+ changed = true;
211
+ onFocusChange?.(oldElement, null, null);
212
+ }
213
+ if (previousElement && subtreeContains(removedRoot, previousElement)) {
214
+ previousElement = null;
215
+ previousId = null;
216
+ changed = true;
217
+ }
218
+ if (changed) notify();
219
+ }
220
+ /**
221
+ * Handle a mounted node update. React can update a focused node from
222
+ * `focusable` to non-focusable without unmounting it; leaving that node as
223
+ * `activeElement` keeps the focused dispatch lane alive and routes later keys
224
+ * into an input owner the app has already deactivated.
225
+ */
226
+ function handleNodeUpdated(updatedNode) {
227
+ let changed = false;
228
+ if (activeElement && updateInvalidatesFocusedNode(updatedNode, activeElement)) {
229
+ const oldElement = activeElement;
230
+ setFocused(oldElement, false);
231
+ syncFocusedSignal(oldElement, false);
232
+ previousElement = activeElement;
233
+ previousId = activeId;
234
+ activeElement = null;
235
+ activeId = null;
236
+ focusOrigin = null;
237
+ changed = true;
238
+ onFocusChange?.(oldElement, null, null);
239
+ }
240
+ if (previousElement && updateInvalidatesFocusedNode(updatedNode, previousElement)) {
241
+ previousElement = null;
242
+ previousId = null;
243
+ changed = true;
244
+ }
245
+ if (changed) notify();
246
+ maybePromoteVirtualFocus(updatedNode);
247
+ }
248
+ /**
249
+ * Promote pending VIRTUAL focus (activeId set, activeElement null) to the
250
+ * real node when a matching focusable target exists under `searchRoot`.
251
+ * Never steals: a real activeElement short-circuits immediately. Resolution
252
+ * mirrors focusById exactly (findByTestID + nearest focusable ancestor), so
253
+ * promoted focus is indistinguishable from a direct hit.
254
+ */
255
+ function maybePromoteVirtualFocus(searchRoot) {
256
+ if (activeId === null || activeElement !== null) return;
257
+ const node = findByTestID(searchRoot, activeId);
258
+ if (!node) return;
259
+ const focusable = findFocusableAncestor(node);
260
+ if (!focusable) return;
261
+ focus(focusable, focusOrigin ?? "programmatic");
262
+ }
263
+ function handleSubtreeAttached(attachedRoot) {
264
+ maybePromoteVirtualFocus(attachedRoot);
265
+ }
266
+ function enterScope(scopeId) {
267
+ scopeStack.push(scopeId);
268
+ notify();
269
+ }
270
+ function exitScope() {
271
+ if (scopeStack.pop() === void 0) return;
272
+ notify();
273
+ }
274
+ function activateScope(scopeId, root) {
275
+ if (activeScopeId && activeId) scopeMemory[activeScopeId] = activeId;
276
+ activeScopeId = scopeId;
277
+ const countBefore = notifyCount;
278
+ const remembered = scopeMemory[scopeId];
279
+ if (remembered) focusById(remembered, root, "programmatic");
280
+ else {
281
+ const scopeNode = findByTestID(root, scopeId);
282
+ if (scopeNode) {
283
+ const order = getTabOrder$1(root, scopeNode);
284
+ if (order.length > 0) focus(order[0], "programmatic");
285
+ }
286
+ }
287
+ if (notifyCount === countBefore) notify();
288
+ }
289
+ function getFocusPath(root) {
290
+ if (!activeElement) return [];
291
+ const path = [];
292
+ let current = activeElement;
293
+ while (current && current !== root.parent) {
294
+ const id = getTestID(current);
295
+ if (id) path.push(id);
296
+ current = current.parent;
297
+ }
298
+ return path;
299
+ }
300
+ function hasFocusWithin(root, testID) {
301
+ if (!activeElement) return false;
302
+ const target = findByTestID(root, testID);
303
+ if (!target) return false;
304
+ let current = activeElement;
305
+ while (current) {
306
+ if (current === target) return true;
307
+ current = current.parent;
308
+ }
309
+ return false;
310
+ }
311
+ /**
312
+ * Resolve the effective scope node for tab navigation.
313
+ * If an explicit scope is provided, use it. Otherwise, if the scopeStack
314
+ * is non-empty, find the topmost scope node in the tree by testID.
315
+ */
316
+ function resolveScope(root, explicitScope) {
317
+ if (explicitScope) return explicitScope;
318
+ if (scopeStack.length > 0) {
319
+ const scopeId = scopeStack[scopeStack.length - 1];
320
+ const scopeNode = findByTestID(root, scopeId);
321
+ if (scopeNode) return scopeNode;
322
+ }
323
+ }
324
+ function buildTabEntries(root, scope) {
325
+ const effectiveScope = resolveScope(root, scope);
326
+ const nodes = getTabOrder$1(root, effectiveScope);
327
+ const entries = nodes.map((node) => ({
328
+ kind: "node",
329
+ node
330
+ }));
331
+ if (!effectiveScope && hookFocusEnabled) {
332
+ const treeIds = new Set(nodes.map((n) => n.props.testID).filter(Boolean));
333
+ for (const entry of hookFocusables) if (entry.isActive && !treeIds.has(entry.id)) entries.push({
334
+ kind: "hook",
335
+ id: entry.id
336
+ });
337
+ }
338
+ return entries;
339
+ }
340
+ function currentTabIndex(entries) {
341
+ if (activeElement) {
342
+ for (let i = 0; i < entries.length; i++) {
343
+ const e = entries[i];
344
+ if (e.kind === "node" && e.node === activeElement) return i;
345
+ }
346
+ return -1;
347
+ }
348
+ if (activeId) for (let i = 0; i < entries.length; i++) {
349
+ const e = entries[i];
350
+ if (e.kind === "hook" && e.id === activeId) return i;
351
+ }
352
+ return -1;
353
+ }
354
+ function focusTabEntry(entry, origin) {
355
+ if (entry.kind === "node") focus(entry.node, origin);
356
+ else focusVirtualId(entry.id, origin);
357
+ }
358
+ function focusNext(root, scope) {
359
+ const entries = buildTabEntries(root, scope);
360
+ if (entries.length === 0) return;
361
+ const currentIndex = currentTabIndex(entries);
362
+ if (currentIndex === -1) {
363
+ focusTabEntry(entries[0], "keyboard");
364
+ return;
365
+ }
366
+ focusTabEntry(entries[(currentIndex + 1) % entries.length], "keyboard");
367
+ }
368
+ function focusPrev(root, scope) {
369
+ const entries = buildTabEntries(root, scope);
370
+ if (entries.length === 0) return;
371
+ const currentIndex = currentTabIndex(entries);
372
+ if (currentIndex === -1) {
373
+ focusTabEntry(entries[entries.length - 1], "keyboard");
374
+ return;
375
+ }
376
+ focusTabEntry(entries[currentIndex <= 0 ? entries.length - 1 : currentIndex - 1], "keyboard");
377
+ }
378
+ function focusDirection(root, direction, layoutFn) {
379
+ if (!activeElement) return;
380
+ const explicitTarget = getExplicitFocusLink(activeElement, direction);
381
+ if (explicitTarget) {
382
+ focusById(explicitTarget, root, "keyboard");
383
+ return;
384
+ }
385
+ const candidates = getTabOrder$1(root);
386
+ const target = findSpatialTarget(activeElement, direction, candidates, layoutFn ?? ((node) => node.scrollRect));
387
+ if (target) focus(target, "keyboard");
388
+ }
389
+ function subscribe(listener) {
390
+ listeners.add(listener);
391
+ return () => {
392
+ listeners.delete(listener);
393
+ };
394
+ }
395
+ function getSnapshot() {
396
+ if (!snapshot) snapshot = {
397
+ activeId,
398
+ previousId,
399
+ focusOrigin,
400
+ scopeStack: [...scopeStack],
401
+ activeScopeId
402
+ };
403
+ return snapshot;
404
+ }
405
+ return {
406
+ get activeElement() {
407
+ return activeElement;
408
+ },
409
+ get activeId() {
410
+ return activeId;
411
+ },
412
+ get previousElement() {
413
+ return previousElement;
414
+ },
415
+ get previousId() {
416
+ return previousId;
417
+ },
418
+ get focusOrigin() {
419
+ return focusOrigin;
420
+ },
421
+ get scopeStack() {
422
+ return [...scopeStack];
423
+ },
424
+ get scopeMemory() {
425
+ return scopeMemory;
426
+ },
427
+ get activeScopeId() {
428
+ return activeScopeId;
429
+ },
430
+ focus,
431
+ focusById,
432
+ focusVirtualId,
433
+ blur,
434
+ handleSubtreeRemoved,
435
+ handleNodeUpdated,
436
+ handleSubtreeAttached,
437
+ enterScope,
438
+ exitScope,
439
+ activateScope,
440
+ getFocusPath,
441
+ hasFocusWithin,
442
+ focusNext,
443
+ focusPrev,
444
+ focusDirection,
445
+ subscribe,
446
+ getSnapshot,
447
+ registerHookFocusable,
448
+ setHookFocusableActive,
449
+ get hasHookFocusables() {
450
+ return hookFocusables.length > 0;
451
+ },
452
+ get hookFocusEnabled() {
453
+ return hookFocusEnabled;
454
+ },
455
+ setHookFocusEnabled
456
+ };
457
+ }
458
+ //#endregion
459
+ //#region packages/headless/src/selection.ts
460
+ /**
461
+ * Check if a character is a word character (not whitespace/punctuation).
462
+ * Word chars: letters, digits, underscore (matching \w).
463
+ */
464
+ function isWordChar(ch) {
465
+ return /\w/.test(ch);
466
+ }
467
+ /**
468
+ * Find word boundaries at a given column in the buffer row.
469
+ * Returns { startCol, endCol } inclusive of the word.
470
+ * If the position is on whitespace/punctuation, selects that single char.
471
+ */
472
+ function findWordBoundary(buffer, col, row) {
473
+ const width = buffer.width;
474
+ const ch = buffer.getCell(col, row).char;
475
+ if (isWordChar(ch)) {
476
+ let startCol = col;
477
+ while (startCol > 0 && isWordChar(buffer.getCell(startCol - 1, row).char)) startCol--;
478
+ let endCol = col;
479
+ while (endCol < width - 1 && isWordChar(buffer.getCell(endCol + 1, row).char)) endCol++;
480
+ return {
481
+ startCol,
482
+ endCol
483
+ };
484
+ }
485
+ return {
486
+ startCol: col,
487
+ endCol: col
488
+ };
489
+ }
490
+ /**
491
+ * Find line boundaries for a given row.
492
+ * Returns { startCol, endCol } spanning from first content to last content.
493
+ * If the row is empty, returns { startCol: 0, endCol: width - 1 }.
494
+ */
495
+ function findLineBoundary(buffer, row) {
496
+ const width = buffer.width;
497
+ let endCol = width - 1;
498
+ while (endCol > 0 && buffer.getCell(endCol, row).char.trim() === "") endCol--;
499
+ let startCol = 0;
500
+ while (startCol < endCol && buffer.getCell(startCol, row).char.trim() === "") startCol++;
501
+ if (startCol >= endCol && buffer.getCell(startCol, row).char.trim() === "") return {
502
+ startCol: 0,
503
+ endCol: width - 1
504
+ };
505
+ return {
506
+ startCol,
507
+ endCol
508
+ };
509
+ }
510
+ /**
511
+ * Extend the head position according to the current granularity.
512
+ * For word granularity: snaps to word boundaries.
513
+ * For line granularity: snaps to line boundaries.
514
+ */
515
+ function extendByGranularity(col, row, anchor, granularity, buffer) {
516
+ if (granularity === "character" || !buffer) return {
517
+ col,
518
+ row
519
+ };
520
+ if (granularity === "word") {
521
+ const { startCol, endCol } = findWordBoundary(buffer, col, row);
522
+ return row > anchor.row || row === anchor.row && col >= anchor.col ? {
523
+ col: endCol,
524
+ row
525
+ } : {
526
+ col: startCol,
527
+ row
528
+ };
529
+ }
530
+ if (granularity === "line") {
531
+ const { startCol, endCol } = findLineBoundary(buffer, row);
532
+ return row > anchor.row || row === anchor.row && col >= anchor.col ? {
533
+ col: endCol,
534
+ row
535
+ } : {
536
+ col: startCol,
537
+ row
538
+ };
539
+ }
540
+ return {
541
+ col,
542
+ row
543
+ };
544
+ }
545
+ function createTerminalSelectionState() {
546
+ return {
547
+ range: null,
548
+ selecting: false,
549
+ source: null,
550
+ granularity: "character",
551
+ scope: null
552
+ };
553
+ }
554
+ /**
555
+ * Clamp a position to a scope boundary.
556
+ */
557
+ function clampToScope(col, row, scope) {
558
+ if (!scope) return {
559
+ col,
560
+ row
561
+ };
562
+ return {
563
+ col: Math.max(scope.left, Math.min(scope.right, col)),
564
+ row: Math.max(scope.top, Math.min(scope.bottom, row))
565
+ };
566
+ }
567
+ function terminalSelectionUpdate(action, state) {
568
+ switch (action.type) {
569
+ case "start": {
570
+ const scope = action.scope ?? null;
571
+ const pos = clampToScope(action.col, action.row, scope);
572
+ return [{
573
+ range: {
574
+ anchor: pos,
575
+ head: pos
576
+ },
577
+ selecting: true,
578
+ source: action.source ?? "mouse",
579
+ granularity: "character",
580
+ scope
581
+ }, [{ type: "render" }]];
582
+ }
583
+ case "startWord": {
584
+ const scope = action.scope ?? null;
585
+ const { startCol, endCol } = findWordBoundary(action.buffer, action.col, action.row);
586
+ return [{
587
+ range: {
588
+ anchor: clampToScope(startCol, action.row, scope),
589
+ head: clampToScope(endCol, action.row, scope)
590
+ },
591
+ selecting: true,
592
+ source: action.source ?? "mouse",
593
+ granularity: "word",
594
+ scope
595
+ }, [{ type: "render" }]];
596
+ }
597
+ case "startLine": {
598
+ const scope = action.scope ?? null;
599
+ const { startCol, endCol } = findLineBoundary(action.buffer, action.row);
600
+ return [{
601
+ range: {
602
+ anchor: clampToScope(startCol, action.row, scope),
603
+ head: clampToScope(endCol, action.row, scope)
604
+ },
605
+ selecting: true,
606
+ source: action.source ?? "mouse",
607
+ granularity: "line",
608
+ scope
609
+ }, [{ type: "render" }]];
610
+ }
611
+ case "extend": {
612
+ if (!state.selecting) return [state, []];
613
+ const scope = "scope" in action ? action.scope ?? null : state.scope;
614
+ const extended = extendByGranularity(action.col, action.row, state.range.anchor, state.granularity, action.buffer);
615
+ const head = clampToScope(extended.col, extended.row, scope);
616
+ return [{
617
+ ...state,
618
+ range: {
619
+ anchor: state.range.anchor,
620
+ head
621
+ },
622
+ scope,
623
+ selecting: true
624
+ }, [{ type: "render" }]];
625
+ }
626
+ case "finish":
627
+ if (!state.range) return [{
628
+ ...state,
629
+ selecting: false
630
+ }, []];
631
+ return [{
632
+ ...state,
633
+ range: state.range,
634
+ selecting: false
635
+ }, []];
636
+ case "clear": {
637
+ const hadRange = state.range !== null;
638
+ return [createTerminalSelectionState(), hadRange ? [{ type: "render" }] : []];
639
+ }
640
+ }
641
+ }
642
+ function normalizeRange(range) {
643
+ const { anchor, head } = range;
644
+ if (anchor.row < head.row || anchor.row === head.row && anchor.col <= head.col) return {
645
+ startRow: anchor.row,
646
+ startCol: anchor.col,
647
+ endRow: head.row,
648
+ endCol: head.col
649
+ };
650
+ return {
651
+ startRow: head.row,
652
+ startCol: head.col,
653
+ endRow: anchor.row,
654
+ endCol: anchor.col
655
+ };
656
+ }
657
+ /**
658
+ * Extract text from a buffer within a selection range.
659
+ *
660
+ * Handles:
661
+ * - Soft-wrap joining (via RowMetadata.softWrapped) — rejoins wrapped visual
662
+ * rows into their logical line, reinserting the word-wrap break space
663
+ * (RowMetadata.wrapJoinSpace) that trim-mode rendering stripped
664
+ * - Trailing fill exclusion (clamp to RowMetadata.lastContentCol; falls back
665
+ * to a trailing-whitespace trim when a row carries no metadata)
666
+ * - Blank line preservation within selection
667
+ * - Wide-char continuation cell skipping
668
+ * - SELECTABLE_FLAG filtering (when respectSelectableFlag is true)
669
+ * - Contain-scope clipping (when options.scope is set)
670
+ */
671
+ function extractText(buffer, range, options) {
672
+ const { startRow, startCol, endRow, endCol } = normalizeRange(range);
673
+ const respectSelectable = options?.respectSelectableFlag ?? true;
674
+ const rowMeta = options?.rowMetadata;
675
+ const scope = options?.scope;
676
+ const parts = [];
677
+ for (let row = startRow; row <= endRow; row++) {
678
+ const meta = rowMeta?.[row];
679
+ let colStart = row === startRow ? startCol : 0;
680
+ let colEnd = row === endRow ? endCol : buffer.width - 1;
681
+ if (scope) {
682
+ colStart = Math.max(colStart, scope.left);
683
+ colEnd = Math.min(colEnd, scope.right);
684
+ if (colStart > colEnd) {
685
+ if (!(meta?.softWrapped && row < endRow)) {
686
+ parts.push("");
687
+ if (row < endRow) parts.push("\n");
688
+ }
689
+ continue;
690
+ }
691
+ }
692
+ if (meta && meta.lastContentCol >= 0) colEnd = Math.min(colEnd, meta.lastContentCol);
693
+ let line = "";
694
+ for (let col = colStart; col <= colEnd; col++) {
695
+ if (buffer.isCellContinuation(col, row)) continue;
696
+ if (respectSelectable && !buffer.isCellSelectable(col, row)) continue;
697
+ line += buffer.getCellChar(col, row);
698
+ }
699
+ if (!meta || meta.lastContentCol < 0) line = line.replace(/\s+$/, "");
700
+ if (meta?.softWrapped && row < endRow) {
701
+ parts.push(line);
702
+ if (meta.wrapJoinSpace) parts.push(" ");
703
+ } else {
704
+ parts.push(line);
705
+ if (row < endRow) parts.push("\n");
706
+ }
707
+ }
708
+ return parts.join("");
709
+ }
710
+ //#endregion
711
+ //#region packages/ag-term/src/drag-events.ts
712
+ /**
713
+ * Create a DragEvent payload.
714
+ */
715
+ function createDragEvent(source, position, dropTarget) {
716
+ return {
717
+ source,
718
+ position,
719
+ dropTarget
720
+ };
721
+ }
722
+ /**
723
+ * Create initial DragState when a drag starts.
724
+ */
725
+ function createDragState(source, startPos) {
726
+ return {
727
+ active: true,
728
+ source,
729
+ startPos,
730
+ currentPos: startPos,
731
+ dropTarget: null
732
+ };
733
+ }
734
+ /**
735
+ * Check if a node accepts drops (has any drag event handler).
736
+ */
737
+ function isDropTarget(node) {
738
+ if (!node) return false;
739
+ const props = node.props;
740
+ return !!(props.onDragEnter || props.onDragLeave || props.onDragOver || props.onDrop);
741
+ }
742
+ /**
743
+ * Find the nearest ancestor (including self) that is a drop target.
744
+ * Returns null if no ancestor accepts drops.
745
+ */
746
+ function findDropTarget(node) {
747
+ let current = node;
748
+ while (current) {
749
+ if (isDropTarget(current)) return current;
750
+ current = current.parent;
751
+ }
752
+ return null;
753
+ }
754
+ //#endregion
755
+ //#region packages/ag-react/src/hooks/useCursor.ts
756
+ /**
757
+ * useCursor - Show and position the terminal's blinking cursor.
758
+ *
759
+ * Maps component-relative (col, row) to absolute terminal coordinates
760
+ * using useScrollRect. Per-instance last-writer-wins: only one cursor
761
+ * can be active at a time per silvery instance (the terminal has one hardware cursor).
762
+ *
763
+ * Cursor state is isolated per silvery instance via CursorContext. Each runtime
764
+ * (run(), createApp(), test renderer) provides its own CursorProvider so
765
+ * multiple silvery instances don't fight over cursor position.
766
+ *
767
+ * Falls back to module-level globals when no CursorProvider is present
768
+ * (backward compatibility / deprecation path).
769
+ */
770
+ /**
771
+ * Unique-id counter for `useCursor` instances. Module-level so the
772
+ * counter is shared across ALL instances in the process — guarantees
773
+ * uniqueness regardless of CursorProvider scope. Wraps at MAX_SAFE_INTEGER
774
+ * (practically infinite for terminal-UI lifetimes).
775
+ */
776
+ let cursorInstanceIdCounter = 0;
777
+ /**
778
+ * Create an isolated cursor store. Each silvery instance gets one.
779
+ * Returns the store (for CursorProvider) and accessors (for scheduler/output).
780
+ */
781
+ function createCursorStore() {
782
+ const store = {
783
+ state: null,
784
+ listeners: /* @__PURE__ */ new Set(),
785
+ accessors: null,
786
+ setCursorState(s) {
787
+ store.state = s;
788
+ for (const listener of store.listeners) listener();
789
+ }
790
+ };
791
+ store.accessors = {
792
+ getCursorState: () => store.state,
793
+ subscribeCursor: (listener) => {
794
+ store.listeners.add(listener);
795
+ return () => {
796
+ store.listeners.delete(listener);
797
+ };
798
+ }
799
+ };
800
+ return store;
801
+ }
802
+ const CursorCtx = createContext(null);
803
+ /**
804
+ * Provider that gives its subtree an isolated cursor store.
805
+ * Wrap your silvery app root in this to isolate cursor state per instance.
806
+ */
807
+ function CursorProvider({ store, children }) {
808
+ return React.createElement(CursorCtx.Provider, { value: store }, children);
809
+ }
810
+ let _globalCursorState = null;
811
+ let _globalCursorListeners = /* @__PURE__ */ new Set();
812
+ function globalSetCursorState(state) {
813
+ _globalCursorState = state;
814
+ for (const listener of _globalCursorListeners) listener();
815
+ }
816
+ function globalGetCursorState() {
817
+ return _globalCursorState;
818
+ }
819
+ /** For testing -- reset global fallback state between tests. */
820
+ function resetCursorState() {
821
+ _globalCursorState = null;
822
+ _globalCursorListeners = /* @__PURE__ */ new Set();
823
+ }
824
+ /**
825
+ * Show and position the terminal's blinking cursor within this component.
826
+ *
827
+ * The cursor position is relative to the component's screen position.
828
+ * Only one cursor can be active per silvery instance -- last caller with visible=true wins.
829
+ *
830
+ * Uses CursorContext if a CursorProvider is present (per-instance isolation).
831
+ * Falls back to module-level globals otherwise (backward compat).
832
+ *
833
+ * @deprecated Phase 2 of `km-silvery.view-as-layout-output` migrated cursor
834
+ * positioning from a React-effect-chain (`useCursor` → `useScrollRect` →
835
+ * `setCursorState`) to a layout-output prop (`<Box cursorOffset={…}>`). The
836
+ * prop path resolves absolute coordinates synchronously during the layout
837
+ * phase, so the very first frame after a conditional mount emits correct
838
+ * cursor ANSI — fixing `km-silvercode.cursor-startup-position` end-to-end.
839
+ *
840
+ * Migrate consumers by deleting the `useCursor({col, row, visible})` call
841
+ * and adding `cursorOffset={{col, row, visible}}` to the surrounding Box. The
842
+ * layout phase applies border + padding offsets automatically, so consumers
843
+ * no longer need to add `borderColOffset` / `borderRowOffset` manually.
844
+ *
845
+ * This hook remains as a back-compat wrapper that writes to the cursor
846
+ * store; the scheduler still reads the store as a fallback when no
847
+ * `cursorOffset` prop is set on any node. Slated for deletion once all
848
+ * in-tree consumers migrate (TextArea + TextInput already migrated; Ink
849
+ * compat goes through the store directly and is unaffected). See bead
850
+ * `km-silvery.delete-cursor-globals`.
851
+ */
852
+ function useCursor(position) {
853
+ const { col, row, visible = true, shape } = position;
854
+ const store = useContext(CursorCtx);
855
+ const node = useContext(NodeContext);
856
+ const set = store ? store.setCursorState.bind(store) : globalSetCursorState;
857
+ const get = store ? store.accessors.getCursorState : globalGetCursorState;
858
+ const instanceIdRef = useRef(0);
859
+ if (instanceIdRef.current === 0) {
860
+ cursorInstanceIdCounter += 1;
861
+ instanceIdRef.current = cursorInstanceIdCounter;
862
+ }
863
+ const props = node?.props;
864
+ const padLeft = props?.paddingLeft ?? props?.paddingX ?? props?.padding ?? 0;
865
+ const padTop = props?.paddingTop ?? props?.paddingY ?? props?.padding ?? 0;
866
+ const borderLeft = props?.borderStyle ? 1 : 0;
867
+ const borderTop = props?.borderStyle ? 1 : 0;
868
+ const contentOffsetX = borderLeft + padLeft;
869
+ const contentOffsetY = borderTop + padTop;
870
+ const colRef = useRef(col);
871
+ const rowRef = useRef(row);
872
+ const visibleRef = useRef(visible);
873
+ const shapeRef = useRef(shape);
874
+ const setRef = useRef(set);
875
+ const getRef = useRef(get);
876
+ const lastRectRef = useRef(null);
877
+ const contentOffsetXRef = useRef(contentOffsetX);
878
+ const contentOffsetYRef = useRef(contentOffsetY);
879
+ colRef.current = col;
880
+ rowRef.current = row;
881
+ visibleRef.current = visible;
882
+ shapeRef.current = shape;
883
+ setRef.current = set;
884
+ getRef.current = get;
885
+ contentOffsetXRef.current = contentOffsetX;
886
+ contentOffsetYRef.current = contentOffsetY;
887
+ const scrollRect = useScrollRect();
888
+ const hasNode = node != null;
889
+ useEffect(() => {
890
+ if (!hasNode) return;
891
+ lastRectRef.current = scrollRect;
892
+ if (!visibleRef.current) return;
893
+ setRef.current({
894
+ x: scrollRect.x + contentOffsetXRef.current + colRef.current,
895
+ y: scrollRect.y + contentOffsetYRef.current + rowRef.current,
896
+ visible: true,
897
+ shape: shapeRef.current,
898
+ owner: instanceIdRef.current
899
+ });
900
+ }, [
901
+ hasNode,
902
+ scrollRect.x,
903
+ scrollRect.y,
904
+ scrollRect.width,
905
+ scrollRect.height,
906
+ scrollRect
907
+ ]);
908
+ const owns = () => {
909
+ const current = getRef.current();
910
+ if (!current) return false;
911
+ if (current.owner === void 0) return true;
912
+ return current.owner === instanceIdRef.current;
913
+ };
914
+ const ownsRef = useRef(owns);
915
+ ownsRef.current = owns;
916
+ useLayoutEffect(() => {
917
+ const rect = lastRectRef.current;
918
+ if (!rect) return;
919
+ if (visible) set({
920
+ x: rect.x + contentOffsetX + col,
921
+ y: rect.y + contentOffsetY + row,
922
+ visible: true,
923
+ shape,
924
+ owner: instanceIdRef.current
925
+ });
926
+ else if (ownsRef.current()) set(null);
927
+ }, [
928
+ col,
929
+ row,
930
+ contentOffsetX,
931
+ contentOffsetY,
932
+ shape,
933
+ visible,
934
+ set
935
+ ]);
936
+ useEffect(() => {
937
+ return () => {
938
+ if (ownsRef.current()) setRef.current(null);
939
+ };
940
+ }, []);
941
+ }
942
+ createLogger("silvery:render");
943
+ createLogger("@silvery/ag-react");
944
+ //#endregion
945
+ //#region packages/ag-term/src/adapters/canvas-adapter.ts
946
+ const DEFAULT_CONFIG$1 = {
947
+ fontSize: 14,
948
+ fontFamily: "monospace",
949
+ lineHeight: 1.2,
950
+ backgroundColor: "#1e1e1e",
951
+ foregroundColor: "#d4d4d4",
952
+ monospace: true,
953
+ dpr: 1
954
+ };
955
+ const BORDER_CHARS$1 = {
956
+ single: {
957
+ topLeft: "┌",
958
+ topRight: "┐",
959
+ bottomLeft: "└",
960
+ bottomRight: "┘",
961
+ horizontal: "─",
962
+ vertical: "│"
963
+ },
964
+ double: {
965
+ topLeft: "╔",
966
+ topRight: "╗",
967
+ bottomLeft: "╚",
968
+ bottomRight: "╝",
969
+ horizontal: "═",
970
+ vertical: "║"
971
+ },
972
+ round: {
973
+ topLeft: "╭",
974
+ topRight: "╮",
975
+ bottomLeft: "╰",
976
+ bottomRight: "╯",
977
+ horizontal: "─",
978
+ vertical: "│"
979
+ },
980
+ bold: {
981
+ topLeft: "┏",
982
+ topRight: "┓",
983
+ bottomLeft: "┗",
984
+ bottomRight: "┛",
985
+ horizontal: "━",
986
+ vertical: "┃"
987
+ }
988
+ };
989
+ /** Create a scratch canvas 2D context for text measurement. */
990
+ function createMeasureContext(fontSize, fontFamily) {
991
+ const canvas = typeof document !== "undefined" ? document.createElement("canvas") : typeof OffscreenCanvas !== "undefined" ? new OffscreenCanvas(1, 1) : null;
992
+ if (!canvas) throw new Error("Canvas not available for text measurement");
993
+ const ctx = canvas.getContext("2d");
994
+ if (!ctx) throw new Error("Could not get 2d context for measurement");
995
+ ctx.font = `${fontSize}px ${fontFamily}`;
996
+ return ctx;
997
+ }
998
+ function createCanvasMeasurer(config) {
999
+ if (config.monospace) return {
1000
+ measureText(text, _style) {
1001
+ return {
1002
+ width: text.length,
1003
+ height: 1
1004
+ };
1005
+ },
1006
+ getLineHeight(_style) {
1007
+ return 1;
1008
+ }
1009
+ };
1010
+ const lineHeightPx = config.fontSize * config.lineHeight;
1011
+ const ctx = createMeasureContext(config.fontSize, config.fontFamily);
1012
+ return {
1013
+ measureText(text, _style) {
1014
+ return {
1015
+ width: ctx.measureText(text).width,
1016
+ height: lineHeightPx
1017
+ };
1018
+ },
1019
+ getLineHeight(_style) {
1020
+ return lineHeightPx;
1021
+ }
1022
+ };
1023
+ }
1024
+ const ANSI_COLORS$1 = {
1025
+ black: "#000000",
1026
+ red: "#cd0000",
1027
+ green: "#00cd00",
1028
+ yellow: "#cdcd00",
1029
+ blue: "#0000ee",
1030
+ magenta: "#cd00cd",
1031
+ cyan: "#00cdcd",
1032
+ white: "#e5e5e5",
1033
+ gray: "#7f7f7f",
1034
+ grey: "#7f7f7f",
1035
+ brightBlack: "#7f7f7f",
1036
+ brightRed: "#ff0000",
1037
+ brightGreen: "#00ff00",
1038
+ brightYellow: "#ffff00",
1039
+ brightBlue: "#5c5cff",
1040
+ brightMagenta: "#ff00ff",
1041
+ brightCyan: "#00ffff",
1042
+ brightWhite: "#ffffff"
1043
+ };
1044
+ function resolveColor$1(color, fallback) {
1045
+ if (!color) return fallback;
1046
+ if (color.startsWith("#") || color.startsWith("rgb")) return color;
1047
+ const named = ANSI_COLORS$1[color.toLowerCase()];
1048
+ if (named) return named;
1049
+ return color;
1050
+ }
1051
+ var CanvasRenderBuffer = class {
1052
+ width;
1053
+ height;
1054
+ canvas;
1055
+ ctx;
1056
+ config;
1057
+ charWidth;
1058
+ cellHeight;
1059
+ constructor(width, height, config) {
1060
+ this.width = width;
1061
+ this.height = height;
1062
+ this.config = config;
1063
+ const dpr = config.dpr;
1064
+ let cssWidth;
1065
+ let cssHeight;
1066
+ if (config.monospace) {
1067
+ this.charWidth = config.fontSize * .6;
1068
+ this.cellHeight = config.fontSize * config.lineHeight;
1069
+ cssWidth = width * this.charWidth;
1070
+ cssHeight = height * this.cellHeight;
1071
+ } else {
1072
+ this.charWidth = 1;
1073
+ this.cellHeight = 1;
1074
+ cssWidth = width;
1075
+ cssHeight = height;
1076
+ }
1077
+ const nativeWidth = Math.ceil(cssWidth * dpr);
1078
+ const nativeHeight = Math.ceil(cssHeight * dpr);
1079
+ if (typeof OffscreenCanvas !== "undefined") this.canvas = new OffscreenCanvas(nativeWidth, nativeHeight);
1080
+ else if (typeof document !== "undefined") {
1081
+ this.canvas = document.createElement("canvas");
1082
+ this.canvas.width = nativeWidth;
1083
+ this.canvas.height = nativeHeight;
1084
+ } else throw new Error("Canvas not available");
1085
+ const ctx = this.canvas.getContext("2d");
1086
+ if (!ctx) throw new Error("Could not get 2d context");
1087
+ this.ctx = ctx;
1088
+ if (dpr !== 1) this.ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
1089
+ this.ctx.fillStyle = config.backgroundColor;
1090
+ this.ctx.fillRect(0, 0, cssWidth, cssHeight);
1091
+ }
1092
+ fillRect(x, y, width, height, style) {
1093
+ if (style.bg) {
1094
+ const px = x * this.charWidth;
1095
+ const py = y * this.cellHeight;
1096
+ const pw = width * this.charWidth;
1097
+ const ph = height * this.cellHeight;
1098
+ this.ctx.fillStyle = resolveColor$1(style.bg, this.config.backgroundColor);
1099
+ this.ctx.fillRect(px, py, pw, ph);
1100
+ }
1101
+ }
1102
+ drawText(x, y, text, style) {
1103
+ const px = x * this.charWidth;
1104
+ const attrs = style.attrs ?? {};
1105
+ const weight = attrs.bold ? "bold" : "normal";
1106
+ const fontStyle = attrs.italic ? "italic" : "normal";
1107
+ this.ctx.font = `${fontStyle} ${weight} ${this.config.fontSize}px ${this.config.fontFamily}`;
1108
+ let py = y * this.cellHeight;
1109
+ this.ctx.fillStyle = resolveColor$1(style.fg, this.config.foregroundColor);
1110
+ if (!this.config.monospace) {
1111
+ const metrics = this.ctx.measureText("Hg");
1112
+ const fontAscent = metrics.fontBoundingBoxAscent;
1113
+ const contentArea = fontAscent + metrics.fontBoundingBoxDescent;
1114
+ const halfLeading = (this.config.fontSize * this.config.lineHeight - contentArea) / 2;
1115
+ this.ctx.textBaseline = "alphabetic";
1116
+ py += halfLeading + fontAscent;
1117
+ } else this.ctx.textBaseline = "top";
1118
+ this.ctx.fillText(text, px, py);
1119
+ if (attrs.underline) this.drawUnderline(px, py, text, style);
1120
+ if (attrs.strikethrough) {
1121
+ const textWidth = this.ctx.measureText(text).width;
1122
+ const strikeY = py + this.config.fontSize * .5;
1123
+ this.ctx.strokeStyle = resolveColor$1(style.fg, this.config.foregroundColor);
1124
+ this.ctx.lineWidth = 1;
1125
+ this.ctx.beginPath();
1126
+ this.ctx.moveTo(px, strikeY);
1127
+ this.ctx.lineTo(px + textWidth, strikeY);
1128
+ this.ctx.stroke();
1129
+ }
1130
+ }
1131
+ /**
1132
+ * Draw underline decorations at pixel coordinates.
1133
+ * Note: px, py are already in pixel coordinates.
1134
+ */
1135
+ drawUnderline(px, py, text, style) {
1136
+ const attrs = style.attrs ?? {};
1137
+ const textWidth = this.ctx.measureText(text).width;
1138
+ const underlineY = py + this.config.fontSize * .9;
1139
+ const underlineColor = resolveColor$1(attrs.underlineColor ?? style.fg, this.config.foregroundColor);
1140
+ this.ctx.strokeStyle = underlineColor;
1141
+ this.ctx.lineWidth = 1;
1142
+ switch (attrs.underlineStyle ?? "single") {
1143
+ case "double":
1144
+ this.ctx.beginPath();
1145
+ this.ctx.moveTo(px, underlineY - 1);
1146
+ this.ctx.lineTo(px + textWidth, underlineY - 1);
1147
+ this.ctx.moveTo(px, underlineY + 1);
1148
+ this.ctx.lineTo(px + textWidth, underlineY + 1);
1149
+ this.ctx.stroke();
1150
+ break;
1151
+ case "curly":
1152
+ this.ctx.beginPath();
1153
+ this.ctx.moveTo(px, underlineY);
1154
+ const waveLength = 4;
1155
+ const amplitude = 2;
1156
+ for (let wx = 0; wx < textWidth; wx += waveLength * 2) {
1157
+ this.ctx.quadraticCurveTo(px + wx + waveLength / 2, underlineY - amplitude, px + wx + waveLength, underlineY);
1158
+ this.ctx.quadraticCurveTo(px + wx + waveLength * 3 / 2, underlineY + amplitude, px + wx + waveLength * 2, underlineY);
1159
+ }
1160
+ this.ctx.stroke();
1161
+ break;
1162
+ case "dotted":
1163
+ this.ctx.setLineDash([2, 2]);
1164
+ this.ctx.beginPath();
1165
+ this.ctx.moveTo(px, underlineY);
1166
+ this.ctx.lineTo(px + textWidth, underlineY);
1167
+ this.ctx.stroke();
1168
+ this.ctx.setLineDash([]);
1169
+ break;
1170
+ case "dashed":
1171
+ this.ctx.setLineDash([4, 2]);
1172
+ this.ctx.beginPath();
1173
+ this.ctx.moveTo(px, underlineY);
1174
+ this.ctx.lineTo(px + textWidth, underlineY);
1175
+ this.ctx.stroke();
1176
+ this.ctx.setLineDash([]);
1177
+ break;
1178
+ default:
1179
+ this.ctx.beginPath();
1180
+ this.ctx.moveTo(px, underlineY);
1181
+ this.ctx.lineTo(px + textWidth, underlineY);
1182
+ this.ctx.stroke();
1183
+ }
1184
+ }
1185
+ drawChar(x, y, char, style) {
1186
+ this.drawText(x, y, char, style);
1187
+ }
1188
+ inBounds(x, y) {
1189
+ return x >= 0 && x < this.width && y >= 0 && y < this.height;
1190
+ }
1191
+ fillRoundedRect(x, y, width, height, radius, fill, stroke, lineWidth = 1) {
1192
+ const px = x * this.charWidth;
1193
+ const py = y * this.cellHeight;
1194
+ const pw = width * this.charWidth;
1195
+ const ph = height * this.cellHeight;
1196
+ const r = Math.min(radius, pw / 2, ph / 2);
1197
+ this.ctx.beginPath();
1198
+ this.ctx.moveTo(px + r, py);
1199
+ this.ctx.lineTo(px + pw - r, py);
1200
+ this.ctx.quadraticCurveTo(px + pw, py, px + pw, py + r);
1201
+ this.ctx.lineTo(px + pw, py + ph - r);
1202
+ this.ctx.quadraticCurveTo(px + pw, py + ph, px + pw - r, py + ph);
1203
+ this.ctx.lineTo(px + r, py + ph);
1204
+ this.ctx.quadraticCurveTo(px, py + ph, px, py + ph - r);
1205
+ this.ctx.lineTo(px, py + r);
1206
+ this.ctx.quadraticCurveTo(px, py, px + r, py);
1207
+ this.ctx.closePath();
1208
+ if (fill) {
1209
+ this.ctx.fillStyle = fill;
1210
+ this.ctx.fill();
1211
+ }
1212
+ if (stroke) {
1213
+ this.ctx.strokeStyle = stroke;
1214
+ this.ctx.lineWidth = lineWidth;
1215
+ this.ctx.stroke();
1216
+ }
1217
+ }
1218
+ };
1219
+ function createCanvasAdapter(config = {}) {
1220
+ const cfg = {
1221
+ ...DEFAULT_CONFIG$1,
1222
+ ...config
1223
+ };
1224
+ return {
1225
+ name: "canvas",
1226
+ measurer: createCanvasMeasurer(cfg),
1227
+ createBuffer(width, height) {
1228
+ return new CanvasRenderBuffer(width, height, cfg);
1229
+ },
1230
+ flush(_buffer, _prevBuffer) {},
1231
+ getBorderChars(style) {
1232
+ return BORDER_CHARS$1[style] ?? BORDER_CHARS$1.single;
1233
+ }
1234
+ };
1235
+ }
1236
+ //#endregion
1237
+ //#region packages/ag-term/src/adapters/dom-adapter.ts
1238
+ const DEFAULT_CONFIG = {
1239
+ fontSize: 14,
1240
+ fontFamily: "monospace",
1241
+ lineHeight: 1.2,
1242
+ backgroundColor: "#1e1e1e",
1243
+ foregroundColor: "#d4d4d4",
1244
+ classPrefix: "silvery"
1245
+ };
1246
+ const BORDER_CHARS = {
1247
+ single: {
1248
+ topLeft: "┌",
1249
+ topRight: "┐",
1250
+ bottomLeft: "└",
1251
+ bottomRight: "┘",
1252
+ horizontal: "─",
1253
+ vertical: "│"
1254
+ },
1255
+ double: {
1256
+ topLeft: "╔",
1257
+ topRight: "╗",
1258
+ bottomLeft: "╚",
1259
+ bottomRight: "╝",
1260
+ horizontal: "═",
1261
+ vertical: "║"
1262
+ },
1263
+ round: {
1264
+ topLeft: "╭",
1265
+ topRight: "╮",
1266
+ bottomLeft: "╰",
1267
+ bottomRight: "╯",
1268
+ horizontal: "─",
1269
+ vertical: "│"
1270
+ },
1271
+ bold: {
1272
+ topLeft: "┏",
1273
+ topRight: "┓",
1274
+ bottomLeft: "┗",
1275
+ bottomRight: "┛",
1276
+ horizontal: "━",
1277
+ vertical: "┃"
1278
+ }
1279
+ };
1280
+ const ANSI_COLORS = {
1281
+ black: "#000000",
1282
+ red: "#cd0000",
1283
+ green: "#00cd00",
1284
+ yellow: "#cdcd00",
1285
+ blue: "#0000ee",
1286
+ magenta: "#cd00cd",
1287
+ cyan: "#00cdcd",
1288
+ white: "#e5e5e5",
1289
+ gray: "#7f7f7f",
1290
+ grey: "#7f7f7f",
1291
+ brightblack: "#7f7f7f",
1292
+ brightred: "#ff0000",
1293
+ brightgreen: "#00ff00",
1294
+ brightyellow: "#ffff00",
1295
+ brightblue: "#5c5cff",
1296
+ brightmagenta: "#ff00ff",
1297
+ brightcyan: "#00ffff",
1298
+ brightwhite: "#ffffff"
1299
+ };
1300
+ function resolveColor(color, fallback) {
1301
+ if (!color) return fallback;
1302
+ if (color.startsWith("#") || color.startsWith("rgb")) return color;
1303
+ return ANSI_COLORS[color.toLowerCase()] ?? color;
1304
+ }
1305
+ function createDOMMeasurer(_config) {
1306
+ return {
1307
+ measureText(text, _style) {
1308
+ return {
1309
+ width: text.length,
1310
+ height: 1
1311
+ };
1312
+ },
1313
+ getLineHeight(_style) {
1314
+ return 1;
1315
+ }
1316
+ };
1317
+ }
1318
+ var DOMRenderBuffer = class {
1319
+ width;
1320
+ height;
1321
+ config;
1322
+ lines;
1323
+ backgrounds;
1324
+ charWidth;
1325
+ cellHeight;
1326
+ container = null;
1327
+ constructor(width, height, config) {
1328
+ this.width = width;
1329
+ this.height = height;
1330
+ this.config = config;
1331
+ this.lines = /* @__PURE__ */ new Map();
1332
+ this.backgrounds = /* @__PURE__ */ new Map();
1333
+ this.charWidth = config.fontSize * .6;
1334
+ this.cellHeight = config.fontSize * config.lineHeight;
1335
+ }
1336
+ /**
1337
+ * Set the container element for rendering.
1338
+ */
1339
+ setContainer(container) {
1340
+ this.container = container;
1341
+ }
1342
+ /**
1343
+ * Get the container element.
1344
+ */
1345
+ getContainer() {
1346
+ return this.container;
1347
+ }
1348
+ fillRect(x, y, width, height, style) {
1349
+ if (style.bg) {
1350
+ const key = `${x},${y},${width},${height}`;
1351
+ this.backgrounds.set(key, {
1352
+ x,
1353
+ y,
1354
+ w: width,
1355
+ h: height,
1356
+ color: resolveColor(style.bg, this.config.backgroundColor)
1357
+ });
1358
+ }
1359
+ }
1360
+ drawText(x, y, text, style) {
1361
+ if (!this.lines.has(y)) this.lines.set(y, []);
1362
+ this.lines.get(y).push({
1363
+ text,
1364
+ style,
1365
+ x
1366
+ });
1367
+ }
1368
+ drawChar(x, y, char, style) {
1369
+ this.drawText(x, y, char, style);
1370
+ }
1371
+ inBounds(x, y) {
1372
+ return x >= 0 && x < this.width && y >= 0 && y < this.height;
1373
+ }
1374
+ /**
1375
+ * Render the buffer to the container element.
1376
+ * Coordinates in the buffer are in cell units (cols/rows).
1377
+ * This method converts them to pixel coordinates for DOM positioning.
1378
+ */
1379
+ render() {
1380
+ if (!this.container) throw new Error("DOMRenderBuffer: No container set. Call setContainer() first.");
1381
+ const container = this.container;
1382
+ const cw = this.charWidth;
1383
+ const ch = this.cellHeight;
1384
+ const containerWidthPx = this.width * cw;
1385
+ const containerHeightPx = this.height * ch;
1386
+ container.innerHTML = "";
1387
+ container.style.cssText = `
1388
+ position: relative;
1389
+ font-family: ${this.config.fontFamily};
1390
+ font-size: ${this.config.fontSize}px;
1391
+ line-height: ${this.config.lineHeight};
1392
+ background-color: ${this.config.backgroundColor};
1393
+ color: ${this.config.foregroundColor};
1394
+ white-space: pre;
1395
+ overflow: hidden;
1396
+ width: ${containerWidthPx}px;
1397
+ height: ${containerHeightPx}px;
1398
+ `;
1399
+ for (const bg of this.backgrounds.values()) {
1400
+ const bgDiv = document.createElement("div");
1401
+ bgDiv.className = `${this.config.classPrefix}-bg`;
1402
+ bgDiv.style.cssText = `
1403
+ position: absolute;
1404
+ left: ${bg.x * cw}px;
1405
+ top: ${bg.y * ch}px;
1406
+ width: ${bg.w * cw}px;
1407
+ height: ${bg.h * ch}px;
1408
+ background-color: ${bg.color};
1409
+ `;
1410
+ container.appendChild(bgDiv);
1411
+ }
1412
+ const sortedLines = Array.from(this.lines.entries()).sort((a, b) => a[0] - b[0]);
1413
+ for (const [y, runs] of sortedLines) {
1414
+ const lineDiv = document.createElement("div");
1415
+ lineDiv.className = `${this.config.classPrefix}-line`;
1416
+ lineDiv.style.cssText = `
1417
+ position: absolute;
1418
+ left: 0;
1419
+ top: ${y * ch}px;
1420
+ height: ${ch}px;
1421
+ white-space: pre;
1422
+ `;
1423
+ const sortedRuns = runs.sort((a, b) => a.x - b.x);
1424
+ for (const run of sortedRuns) {
1425
+ const span = document.createElement(run.style.hyperlink === void 0 ? "span" : "a");
1426
+ span.className = `${this.config.classPrefix}-text`;
1427
+ span.textContent = run.text;
1428
+ if (run.style.hyperlink !== void 0) span.setAttribute("href", run.style.hyperlink);
1429
+ const styles = [`position: absolute`, `left: ${run.x * cw}px`];
1430
+ if (run.style.fg) styles.push(`color: ${resolveColor(run.style.fg, this.config.foregroundColor)}`);
1431
+ if (run.style.bg) styles.push(`background-color: ${resolveColor(run.style.bg, this.config.backgroundColor)}`);
1432
+ const attrs = run.style.attrs;
1433
+ if (attrs) {
1434
+ if (attrs.bold) styles.push("font-weight: bold");
1435
+ if (attrs.dim) styles.push("opacity: 0.5");
1436
+ if (attrs.italic) styles.push("font-style: italic");
1437
+ if (attrs.underline || attrs.underlineStyle) {
1438
+ const underlineStyle = attrs.underlineStyle ?? "single";
1439
+ const underlineColor = attrs.underlineColor ? resolveColor(attrs.underlineColor, this.config.foregroundColor) : "currentColor";
1440
+ switch (underlineStyle) {
1441
+ case "double":
1442
+ styles.push(`text-decoration: underline double ${underlineColor}`);
1443
+ break;
1444
+ case "curly":
1445
+ styles.push(`text-decoration: underline wavy ${underlineColor}`);
1446
+ break;
1447
+ case "dotted":
1448
+ styles.push(`text-decoration: underline dotted ${underlineColor}`);
1449
+ break;
1450
+ case "dashed":
1451
+ styles.push(`text-decoration: underline dashed ${underlineColor}`);
1452
+ break;
1453
+ default: styles.push(`text-decoration: underline solid ${underlineColor}`);
1454
+ }
1455
+ }
1456
+ if (attrs.strikethrough) {
1457
+ const existing = styles.find((s) => s.startsWith("text-decoration:"));
1458
+ if (existing) {
1459
+ const idx = styles.indexOf(existing);
1460
+ styles[idx] = existing.replace("underline", "underline line-through");
1461
+ } else styles.push("text-decoration: line-through");
1462
+ }
1463
+ if (attrs.inverse) {
1464
+ const fg = run.style.fg ? resolveColor(run.style.fg, this.config.foregroundColor) : this.config.foregroundColor;
1465
+ const bg = run.style.bg ? resolveColor(run.style.bg, this.config.backgroundColor) : this.config.backgroundColor;
1466
+ styles.push(`color: ${bg}`, `background-color: ${fg}`);
1467
+ }
1468
+ }
1469
+ span.style.cssText = styles.join("; ");
1470
+ lineDiv.appendChild(span);
1471
+ }
1472
+ container.appendChild(lineDiv);
1473
+ }
1474
+ }
1475
+ /**
1476
+ * Clear the buffer.
1477
+ */
1478
+ clear() {
1479
+ this.lines.clear();
1480
+ this.backgrounds.clear();
1481
+ }
1482
+ };
1483
+ function createDOMAdapter(config = {}) {
1484
+ const cfg = {
1485
+ ...DEFAULT_CONFIG,
1486
+ ...config
1487
+ };
1488
+ return {
1489
+ name: "dom",
1490
+ measurer: createDOMMeasurer(cfg),
1491
+ createBuffer(width, height) {
1492
+ return new DOMRenderBuffer(width, height, cfg);
1493
+ },
1494
+ flush(buffer, _prevBuffer) {
1495
+ const domBuffer = buffer;
1496
+ if (domBuffer.getContainer()) domBuffer.render();
1497
+ },
1498
+ getBorderChars(style) {
1499
+ return BORDER_CHARS[style] ?? BORDER_CHARS.single;
1500
+ }
1501
+ };
1502
+ }
1503
+ let stylesInjected = false;
1504
+ /**
1505
+ * Inject global CSS styles for silvery DOM rendering.
1506
+ * Call once at application startup if you want default styling.
1507
+ */
1508
+ function injectDOMStyles(classPrefix = "silvery") {
1509
+ if (stylesInjected || typeof document === "undefined") return;
1510
+ const style = document.createElement("style");
1511
+ style.textContent = `
1512
+ .${classPrefix}-container {
1513
+ font-family: monospace;
1514
+ white-space: pre;
1515
+ overflow: hidden;
1516
+ }
1517
+ .${classPrefix}-line {
1518
+ white-space: pre;
1519
+ }
1520
+ .${classPrefix}-text {
1521
+ white-space: pre;
1522
+ }
1523
+ /* Selection styling */
1524
+ .${classPrefix}-text::selection {
1525
+ background-color: rgba(100, 150, 255, 0.3);
1526
+ }
1527
+ `;
1528
+ document.head.appendChild(style);
1529
+ stylesInjected = true;
1530
+ }
1531
+ //#endregion
1532
+ //#region packages/ag-term/src/caret-style.ts
1533
+ /**
1534
+ * Walk a tree and return the AgNode whose `cursorOffset` produced the active
1535
+ * caret rect (matching the precedence rules of `findActiveCursorRect`).
1536
+ *
1537
+ * Used by the scheduler to feed `resolveCaretStyle`. Re-walks the tree to
1538
+ * keep `findActiveCursorRect` returning a value-only (data) result rather
1539
+ * than coupling it to the AgNode reference.
1540
+ *
1541
+ * Returns null when no caret is active.
1542
+ */
1543
+ function findActiveCursorNode(root) {
1544
+ let focusedNode = null;
1545
+ let focusedSuppressesFallback = false;
1546
+ let fallbackNode = null;
1547
+ function walk(node) {
1548
+ for (const child of node.children) walk(child);
1549
+ const props = node.props;
1550
+ if (!props?.cursorOffset) return;
1551
+ const offset = props.cursorOffset;
1552
+ if (props.focused === true || node.interactiveState?.focused) {
1553
+ focusedSuppressesFallback = true;
1554
+ focusedNode = node;
1555
+ return;
1556
+ }
1557
+ if (offset.visible !== false) fallbackNode = node;
1558
+ }
1559
+ walk(root);
1560
+ return focusedNode ?? (focusedSuppressesFallback ? null : fallbackNode);
1561
+ }
1562
+ //#endregion
1563
+ //#region packages/ag-term/src/osc-markers.ts
1564
+ /**
1565
+ * OSC 133 Semantic Prompt Markers
1566
+ *
1567
+ * Shell integration protocol that marks prompts and commands in terminal output.
1568
+ * Terminals like iTerm2, Kitty, and WezTerm use these markers to provide
1569
+ * "jump to previous/next prompt" navigation (Cmd+Up/Cmd+Down in iTerm2).
1570
+ *
1571
+ * Protocol: OSC 133
1572
+ * - Prompt start: ESC ] 133 ; A BEL (before user input)
1573
+ * - Prompt end: ESC ] 133 ; B BEL (after user input, before command output)
1574
+ * - Command output start: ESC ] 133 ; C BEL (before command output)
1575
+ * - Command output end: ESC ] 133 ; D ; N BEL (after command output, N = exit code)
1576
+ *
1577
+ * For a chat-style app, each "exchange" (user prompt + assistant response) maps to:
1578
+ * - 133;A before the user's message
1579
+ * - 133;B after the user's message
1580
+ * - 133;C before the assistant's response
1581
+ * - 133;D;0 after the assistant's response
1582
+ *
1583
+ * Supported by: iTerm2, Kitty, WezTerm, foot, Ghostty
1584
+ */
1585
+ const OSC133 = {
1586
+ promptStart: "\x1B]133;A\x07",
1587
+ promptEnd: "\x1B]133;B\x07",
1588
+ commandStart: "\x1B]133;C\x07",
1589
+ commandEnd: (exitCode) => `\x1b]133;D;${exitCode ?? 0}\x07`
1590
+ };
1591
+ //#endregion
1592
+ //#region packages/ag-term/src/kitty-detect.ts
1593
+ /** Regex to match a Kitty keyboard query response: CSI ? <flags> u */
1594
+ const KITTY_RESPONSE_RE = /\x1b\[\?(\d+)u/;
1595
+ /**
1596
+ * Detect Kitty keyboard protocol support.
1597
+ *
1598
+ * Sends CSI ? u to the terminal and waits for a response.
1599
+ * Supported terminals respond with CSI ? flags u.
1600
+ * Unsupported terminals either ignore the query or echo it.
1601
+ *
1602
+ * @param write Function to write to stdout
1603
+ * @param read Function to read a chunk from stdin (should resolve with data or null on timeout)
1604
+ * @param timeoutMs How long to wait for response (default: 200ms)
1605
+ */
1606
+ async function detectKittySupport(write, read, timeoutMs = 200) {
1607
+ write(queryKittyKeyboard());
1608
+ const data = await read(timeoutMs);
1609
+ if (data == null) return {
1610
+ supported: false,
1611
+ flags: 0
1612
+ };
1613
+ const parsed = parseKittyResponse(data);
1614
+ if (!parsed) return {
1615
+ supported: false,
1616
+ flags: 0,
1617
+ buffered: data
1618
+ };
1619
+ const buffered = data.slice(0, parsed.start) + data.slice(parsed.end);
1620
+ return {
1621
+ ...parsed.result,
1622
+ buffered: buffered || void 0
1623
+ };
1624
+ }
1625
+ /** Query Kitty support through the session's canonical stdin owner. */
1626
+ async function detectKittyWithProbe(input, timeoutMs = 200) {
1627
+ return await input.probe({
1628
+ query: queryKittyKeyboard(),
1629
+ timeoutMs,
1630
+ parse: (acc) => {
1631
+ const parsed = parseKittyResponse(acc);
1632
+ return parsed ? {
1633
+ result: parsed.result,
1634
+ consumed: parsed.end
1635
+ } : null;
1636
+ }
1637
+ }) ?? {
1638
+ supported: false,
1639
+ flags: 0
1640
+ };
1641
+ }
1642
+ function parseKittyResponse(data) {
1643
+ const match = KITTY_RESPONSE_RE.exec(data);
1644
+ if (!match) return null;
1645
+ return {
1646
+ result: {
1647
+ supported: true,
1648
+ flags: parseInt(match[1], 10)
1649
+ },
1650
+ start: match.index,
1651
+ end: match.index + match[0].length
1652
+ };
1653
+ }
1654
+ /**
1655
+ * Detect Kitty support using real stdin/stdout.
1656
+ * Convenience wrapper around detectKittySupport.
1657
+ */
1658
+ async function detectKittyFromStdio(stdout, stdin, timeoutMs = 200) {
1659
+ const otherListeners = stdin.listenerCount("data") > 0;
1660
+ const wasRaw = stdin.isRaw;
1661
+ let didSetRaw = false;
1662
+ if (!wasRaw && !otherListeners) {
1663
+ stdin.setRawMode(true);
1664
+ didSetRaw = true;
1665
+ }
1666
+ try {
1667
+ const write = (s) => {
1668
+ stdout.write(s);
1669
+ };
1670
+ const read = (ms) => new Promise((resolve) => {
1671
+ const timer = setTimeout(() => {
1672
+ stdin.removeListener("data", onData);
1673
+ resolve(null);
1674
+ }, ms);
1675
+ function onData(chunk) {
1676
+ clearTimeout(timer);
1677
+ stdin.removeListener("data", onData);
1678
+ resolve(chunk.toString());
1679
+ }
1680
+ stdin.on("data", onData);
1681
+ });
1682
+ return await detectKittySupport(write, read, timeoutMs);
1683
+ } finally {
1684
+ if (didSetRaw) stdin.setRawMode(false);
1685
+ }
1686
+ }
1687
+ //#endregion
1688
+ //#region packages/ag-term/src/termtest.ts
1689
+ /**
1690
+ * Terminal Capability Test
1691
+ *
1692
+ * Renders labeled test patterns for each terminal feature.
1693
+ * Run in any terminal to visually verify what it supports.
1694
+ *
1695
+ * Usage:
1696
+ * import { runTermtest } from "@silvery/ag-react"
1697
+ * runTermtest() // all sections
1698
+ * runTermtest({ sections: ["emoji", "colors"] }) // specific sections
1699
+ *
1700
+ * Compare output across terminals to build/verify profiles.
1701
+ */
1702
+ const CSI = `[`;
1703
+ const RESET = `${CSI}0m`;
1704
+ function sgr(...codes) {
1705
+ return `${CSI}${codes.join(";")}m`;
1706
+ }
1707
+ function sectionHeader(title) {
1708
+ return `\n${sgr(1)}═══ ${title} ═══${RESET}\n`;
1709
+ }
1710
+ function row(label, content) {
1711
+ return ` ${label.padEnd(24)} ${content}${RESET}`;
1712
+ }
1713
+ /** Available test sections */
1714
+ const TERMTEST_SECTIONS = [
1715
+ "sgr",
1716
+ "underline",
1717
+ "colors",
1718
+ "256",
1719
+ "truecolor",
1720
+ "unicode",
1721
+ "emoji",
1722
+ "borders",
1723
+ "inverse",
1724
+ "profile"
1725
+ ];
1726
+ /**
1727
+ * Run the terminal capability test.
1728
+ * Pass section names to filter: `runTermtest({ sections: ["emoji"] })`
1729
+ */
1730
+ function runTermtest(options) {
1731
+ const w = options?.output ?? process.stdout;
1732
+ const filter = options?.sections;
1733
+ const show = (s) => !filter || filter.length === 0 || filter.includes(s);
1734
+ const { caps, emulator } = createTerminalProfile();
1735
+ w.write(`\n${sgr(1)}Terminal Capability Test${RESET}\n`);
1736
+ w.write(` Program: ${emulator.program || "(unknown)"}\n`);
1737
+ w.write(` TERM: ${emulator.TERM || "(unknown)"}\n`);
1738
+ w.write(` COLORTERM: ${process.env.COLORTERM || "(unset)"}\n`);
1739
+ w.write(` Detected: color=${caps.colorLevel} maybe-dark=${caps.maybeDarkBackground} maybe-nerdfont=${caps.maybeNerdFont}\n`);
1740
+ w.write(` Underline: styles=[${caps.underlineStyles.join(", ")}] color=${caps.underlineColor}\n`);
1741
+ w.write(` Maybe wide emojis: ${caps.maybeWideEmojis}\n`);
1742
+ if (show("sgr")) {
1743
+ w.write(sectionHeader("SGR Text Attributes"));
1744
+ w.write(row("Bold", `${sgr(1)}The quick brown fox${RESET}`) + "\n");
1745
+ w.write(row("Dim", `${sgr(2)}The quick brown fox${RESET}`) + "\n");
1746
+ w.write(row("Italic", `${sgr(3)}The quick brown fox${RESET}`) + "\n");
1747
+ w.write(row("Underline", `${sgr(4)}The quick brown fox${RESET}`) + "\n");
1748
+ w.write(row("Strikethrough", `${sgr(9)}The quick brown fox${RESET}`) + "\n");
1749
+ w.write(row("Inverse", `${sgr(7)}The quick brown fox${RESET}`) + "\n");
1750
+ w.write(row("Blink", `${sgr(5)}The quick brown fox${RESET}`) + "\n");
1751
+ w.write(row("Bold+Italic", `${sgr(1, 3)}The quick brown fox${RESET}`) + "\n");
1752
+ }
1753
+ if (show("underline")) {
1754
+ w.write(sectionHeader("SGR 4:x Underline Styles (Terminal.app BREAKS here)"));
1755
+ w.write(row("4:1 Single", `${CSI}4:1mThe quick brown fox${RESET}`) + "\n");
1756
+ w.write(row("4:2 Double", `${CSI}4:2mThe quick brown fox${RESET}`) + "\n");
1757
+ w.write(row("4:3 Curly", `${CSI}4:3mThe quick brown fox${RESET}`) + "\n");
1758
+ w.write(row("4:4 Dotted", `${CSI}4:4mThe quick brown fox${RESET}`) + "\n");
1759
+ w.write(row("4:5 Dashed", `${CSI}4:5mThe quick brown fox${RESET}`) + "\n");
1760
+ w.write(row("After 4:x (clean?)", `${CSI}4:3m${RESET}This text should be normal — if garbled, 4:x corrupted SGR state`) + "\n");
1761
+ w.write(sectionHeader("SGR 58 Underline Color (Terminal.app BREAKS here)"));
1762
+ w.write(row("58;5;1 Red UL", `${sgr(4)}${CSI}58;5;1mThe quick brown fox${RESET}`) + "\n");
1763
+ w.write(row("58;5;2 Green UL", `${sgr(4)}${CSI}58;5;2mThe quick brown fox${RESET}`) + "\n");
1764
+ w.write(row("58;5;4 Blue UL", `${sgr(4)}${CSI}58;5;4mThe quick brown fox${RESET}`) + "\n");
1765
+ w.write(row("58;2;R;G;B TC UL", `${sgr(4)}${CSI}58;2;255;128;0mThe quick brown fox${RESET}`) + "\n");
1766
+ w.write(row("After SGR 58 (clean?)", `${sgr(4)}${CSI}58;5;1m${RESET}This text should be normal — if garbled, 58 corrupted SGR state`) + "\n");
1767
+ }
1768
+ if (show("colors")) {
1769
+ w.write(sectionHeader("ANSI 16 Colors"));
1770
+ const colorNames = [
1771
+ "Black",
1772
+ "Red",
1773
+ "Green",
1774
+ "Yellow",
1775
+ "Blue",
1776
+ "Magenta",
1777
+ "Cyan",
1778
+ "White"
1779
+ ];
1780
+ let fgLine = " FG: ";
1781
+ for (let i = 0; i < 8; i++) fgLine += `${sgr(30 + i)} ${colorNames[i]}${RESET}`;
1782
+ w.write(fgLine + "\n");
1783
+ let fgBrLine = " Br: ";
1784
+ for (let i = 0; i < 8; i++) fgBrLine += `${sgr(90 + i)} ${colorNames[i]}${RESET}`;
1785
+ w.write(fgBrLine + "\n");
1786
+ let bgLine = " BG: ";
1787
+ for (let i = 0; i < 8; i++) bgLine += `${sgr(40 + i)} ${colorNames[i]} ${RESET}`;
1788
+ w.write(bgLine + "\n");
1789
+ let bgBrLine = " BrBG:";
1790
+ for (let i = 0; i < 8; i++) bgBrLine += `${sgr(100 + i)} ${colorNames[i]} ${RESET}`;
1791
+ w.write(bgBrLine + "\n");
1792
+ }
1793
+ if (show("256")) {
1794
+ w.write(sectionHeader("256 Colors (indices 0-15, 16-231, 232-255)"));
1795
+ let stdLine = " 0-15: ";
1796
+ for (let i = 0; i < 16; i++) stdLine += `${CSI}48;5;${i}m ${RESET}`;
1797
+ w.write(stdLine + "\n");
1798
+ let cubeLine = " Cube: ";
1799
+ for (let i = 16; i < 52; i++) cubeLine += `${CSI}48;5;${i}m ${RESET}`;
1800
+ w.write(cubeLine + "\n");
1801
+ let grayLine = " Gray: ";
1802
+ for (let i = 232; i < 256; i++) grayLine += `${CSI}48;5;${i}m ${RESET}`;
1803
+ w.write(grayLine + "\n");
1804
+ }
1805
+ if (show("truecolor")) {
1806
+ w.write(sectionHeader("Truecolor (38;2;R;G;B / 48;2;R;G;B)"));
1807
+ let tcLine = " Gradient: ";
1808
+ for (let i = 0; i < 40; i++) {
1809
+ const r = Math.round(i / 39 * 255);
1810
+ const g = Math.round((39 - i) / 39 * 128);
1811
+ tcLine += `${CSI}48;2;${r};${g};80m ${RESET}`;
1812
+ }
1813
+ w.write(tcLine + "\n");
1814
+ w.write(row("If solid blocks →", "Truecolor NOT supported (256-color fallback)") + "\n");
1815
+ }
1816
+ if (show("unicode")) {
1817
+ w.write(sectionHeader("Unicode, Emoji, PUA (Nerd Fonts)"));
1818
+ w.write(row("ASCII", "Hello World! 0123456789") + "\n");
1819
+ w.write(row("Latin Extended", "àéîõü ñ ß ø å") + "\n");
1820
+ w.write(row("CJK", "你好世界 日本語 한국어") + "\n");
1821
+ w.write(row("Box Drawing", "┌─┬─┐ ╔═╦═╗ ╭─╮") + "\n");
1822
+ w.write(row("Block Elements", "▀▄█▌▐░▒▓") + "\n");
1823
+ w.write(row("Symbols", "● ○ ◉ ▶ ◀ ⚠ ✓ ✗ ⋮ §") + "\n");
1824
+ w.write(row("Emoji", "🎉 🚀 📁 📄 ⭐ 🔥 👍") + "\n");
1825
+ w.write(row("Nerd Font PUA", " folder  file  arrow  gear") + "\n");
1826
+ w.write(row("If PUA = boxes →", "Nerd Fonts not installed") + "\n");
1827
+ }
1828
+ if (show("emoji")) {
1829
+ w.write(sectionHeader("Emoji Width Alignment (_'s should align with ruler)"));
1830
+ w.write(" Ruler: |1234567890|\n");
1831
+ w.write(" ASCII 'A': |A_________| (w=1)\n");
1832
+ w.write(" CJK '你': |你________| (w=2)\n");
1833
+ w.write(" Flag 🇨🇦: |🇨🇦________| (w=2)\n");
1834
+ w.write(" Flag 🇺🇸: |🇺🇸________| (w=2)\n");
1835
+ w.write(" Emoji 📁: |📁________| (w=2)\n");
1836
+ w.write(" Emoji 📄: |📄________| (w=2)\n");
1837
+ w.write(" Emoji 📋: |📋________| (w=2)\n");
1838
+ w.write(" Emoji ⚠️: |⚠️________| (w=2)\n");
1839
+ w.write(" Text ⚠: |⚠_________| (w=1 text, 2 emoji)\n");
1840
+ w.write(" Text ⭐: |⭐________| (w=1 text, 2 emoji)\n");
1841
+ w.write(" Text ☑: |☑_________| (w=1 text, 2 emoji)\n");
1842
+ w.write(" Emoji 🏠: |🏠________| (w=2)\n");
1843
+ w.write(" Emoji 👓: |👓________| (w=2)\n");
1844
+ w.write(" ZWJ 👨🏻‍💻: |👨🏻‍💻________| (w=2)\n");
1845
+ w.write(" Arrow →: |→_________| (w=1)\n");
1846
+ w.write(" Arrow ▸: |▸_________| (w=1)\n");
1847
+ w.write(" Circle ○: |○_________| (w=1)\n");
1848
+ w.write(" Square □: |□_________| (w=1)\n");
1849
+ w.write(" Check ✓: |✓_________| (w=1)\n");
1850
+ }
1851
+ if (show("borders")) {
1852
+ w.write(sectionHeader("Box Drawing Borders"));
1853
+ w.write(" ┌──────────┐ ╔══════════╗ ╭──────────╮\n");
1854
+ w.write(" │ single │ ║ double ║ │ round │\n");
1855
+ w.write(" └──────────┘ ╚══════════╝ ╰──────────╯\n");
1856
+ }
1857
+ if (show("inverse")) {
1858
+ w.write(sectionHeader("Inverse + Background (potential artifact source)"));
1859
+ w.write(row("Red FG + Inverse", `${sgr(31, 7)}This should have red background${RESET}`) + "\n");
1860
+ w.write(row("Cyan BG + White FG", `${sgr(46, 37)}Cyan background, white text${RESET}`) + "\n");
1861
+ w.write(row("Black BG + White FG", `${sgr(40, 97)}Black bg, bright white text${RESET}`) + "\n");
1862
+ w.write(row("White BG + Black FG", `${sgr(107, 30)}White bg, black text${RESET}`) + "\n");
1863
+ w.write(sectionHeader("Reset Sanity Check"));
1864
+ w.write(" This line should be completely normal with no formatting artifacts.\n");
1865
+ w.write(" If you see colors, underlines, or other styling above, the terminal\n");
1866
+ w.write(" failed to process an SGR reset (\\x1b[0m) correctly.\n");
1867
+ }
1868
+ if (show("profile")) {
1869
+ w.write(sectionHeader("Detected Terminal Profile"));
1870
+ const entries = Object.entries(caps);
1871
+ for (const [key, value] of entries) {
1872
+ const indicator = value === true ? "✓" : value === false ? "✗" : String(value);
1873
+ w.write(` ${key.padEnd(20)} ${indicator}\n`);
1874
+ }
1875
+ }
1876
+ w.write("\n");
1877
+ }
1878
+ //#endregion
1879
+ //#region packages/ag-term/src/cursor-query.ts
1880
+ /**
1881
+ * CSI 6n Cursor Position Query
1882
+ *
1883
+ * Queries the terminal for the current cursor position using the standard
1884
+ * Device Status Report (DSR) mechanism.
1885
+ *
1886
+ * Protocol:
1887
+ * - Query: CSI 6 n (\x1b[6n)
1888
+ * - Response: CSI {row} ; {col} R (\x1b[{row};{col}R)
1889
+ *
1890
+ * Row and column are 1-indexed in the protocol response.
1891
+ *
1892
+ * Supported by: virtually all terminals (VT100+)
1893
+ */
1894
+ /** Regex to match a CPR response: CSI row ; col R */
1895
+ const CPR_RESPONSE_RE = /\x1b\[(\d+);(\d+)R/;
1896
+ /**
1897
+ * Query the terminal cursor position.
1898
+ *
1899
+ * Sends CSI 6n and parses the CPR response.
1900
+ * Returns 1-indexed row and column.
1901
+ *
1902
+ * @param write Function to write to stdout
1903
+ * @param read Function to read a chunk from stdin (resolves with data or null on timeout)
1904
+ * @param timeoutMs How long to wait for response (default: 200ms)
1905
+ */
1906
+ async function queryCursorPosition(write, read, timeoutMs = 200) {
1907
+ write("\x1B[6n");
1908
+ const data = await read(timeoutMs);
1909
+ if (data == null) return null;
1910
+ const match = CPR_RESPONSE_RE.exec(data);
1911
+ if (!match) return null;
1912
+ return {
1913
+ row: parseInt(match[1], 10),
1914
+ col: parseInt(match[2], 10)
1915
+ };
1916
+ }
1917
+ /**
1918
+ * Query cursor position using real stdin/stdout.
1919
+ * Convenience wrapper around queryCursorPosition.
1920
+ */
1921
+ async function queryCursorFromStdio(stdout, stdin, timeoutMs = 200) {
1922
+ const otherListeners = stdin.listenerCount("data") > 0;
1923
+ const wasRaw = stdin.isRaw;
1924
+ let didSetRaw = false;
1925
+ if (!wasRaw && !otherListeners) {
1926
+ stdin.setRawMode(true);
1927
+ didSetRaw = true;
1928
+ }
1929
+ try {
1930
+ const write = (s) => {
1931
+ stdout.write(s);
1932
+ };
1933
+ const read = (ms) => new Promise((resolve) => {
1934
+ const timer = setTimeout(() => {
1935
+ stdin.removeListener("data", onData);
1936
+ resolve(null);
1937
+ }, ms);
1938
+ function onData(chunk) {
1939
+ clearTimeout(timer);
1940
+ stdin.removeListener("data", onData);
1941
+ resolve(chunk.toString());
1942
+ }
1943
+ stdin.on("data", onData);
1944
+ });
1945
+ return await queryCursorPosition(write, read, timeoutMs);
1946
+ } finally {
1947
+ if (didSetRaw) stdin.setRawMode(false);
1948
+ }
1949
+ }
1950
+ //#endregion
1951
+ //#region packages/ag-term/src/ansi/width-detection.ts
1952
+ /** Well-known xterm width DEC mode numbers. */
1953
+ const WidthMode = {
1954
+ UTF8: 1020,
1955
+ CJK_WIDTH: 1021,
1956
+ EMOJI_WIDTH: 1022,
1957
+ PRIVATE_USE_WIDTH: 1023
1958
+ };
1959
+ /** Default configuration when detection fails or times out. */
1960
+ const DEFAULT_WIDTH_CONFIG = {
1961
+ utf8: true,
1962
+ cjkWidth: 1,
1963
+ emojiWidth: 2,
1964
+ privateUseWidth: 1
1965
+ };
1966
+ /** Regex for DECRPM response: CSI ? mode ; Ps $ y */
1967
+ const DECRPM_RE = /\x1b\[\?(\d+);(\d+)\$y/g;
1968
+ /**
1969
+ * Parse a DECRPM response value into a boolean (set/reset).
1970
+ * 1 and 3 (permanently set) = true, everything else = false.
1971
+ */
1972
+ function isSet(ps) {
1973
+ return ps === 1 || ps === 3;
1974
+ }
1975
+ /**
1976
+ * Query width modes through the session's canonical stdin owner.
1977
+ *
1978
+ * Unlike `createWidthDetector`'s standalone transport adapter, this path
1979
+ * never attaches another `data` listener: responses are consumed by the
1980
+ * owner's probe lane before normal key/mouse parsing.
1981
+ */
1982
+ function detectWidthConfigWithProbe(input, timeoutMs = 200) {
1983
+ return detectWidthConfig((mode) => input.probe({
1984
+ query: `\x1b[?${mode}$p`,
1985
+ timeoutMs,
1986
+ parse: (acc) => parseWidthModeResponse(acc, mode)
1987
+ }));
1988
+ }
1989
+ function parseWidthModeResponse(acc, expectedMode) {
1990
+ DECRPM_RE.lastIndex = 0;
1991
+ let match;
1992
+ while ((match = DECRPM_RE.exec(acc)) !== null) {
1993
+ if (parseInt(match[1], 10) !== expectedMode) continue;
1994
+ return {
1995
+ result: parseInt(match[2], 10),
1996
+ consumed: match.index + match[0].length
1997
+ };
1998
+ }
1999
+ return null;
2000
+ }
2001
+ async function detectWidthConfig(query) {
2002
+ const utf8Ps = await query(WidthMode.UTF8);
2003
+ const cjkPs = await query(WidthMode.CJK_WIDTH);
2004
+ const emojiPs = await query(WidthMode.EMOJI_WIDTH);
2005
+ const puaPs = await query(WidthMode.PRIVATE_USE_WIDTH);
2006
+ return {
2007
+ utf8: utf8Ps !== null ? isSet(utf8Ps) : DEFAULT_WIDTH_CONFIG.utf8,
2008
+ cjkWidth: cjkPs !== null ? isSet(cjkPs) ? 2 : 1 : DEFAULT_WIDTH_CONFIG.cjkWidth,
2009
+ emojiWidth: emojiPs !== null ? isSet(emojiPs) ? 2 : 1 : DEFAULT_WIDTH_CONFIG.emojiWidth,
2010
+ privateUseWidth: puaPs !== null ? isSet(puaPs) ? 2 : 1 : DEFAULT_WIDTH_CONFIG.privateUseWidth
2011
+ };
2012
+ }
2013
+ /**
2014
+ * Apply detected width configuration to terminal capabilities.
2015
+ *
2016
+ * Post km-silvery.plateau-naming-polish (2026-04-23): `maybeWideEmojis` and
2017
+ * `textSizing` both live on caps, so the overlay returns a single updated
2018
+ * caps object. The mapping is:
2019
+ * - emojiWidth=2 → caps.maybeWideEmojis=true
2020
+ * - privateUseWidth=2 → caps.textSizing=true (PUA treated as 2-wide)
2021
+ *
2022
+ * CJK width and UTF-8 mode are informational — they don't yet map to caps
2023
+ * fields but are available in TerminalWidthConfig for consumers.
2024
+ */
2025
+ function applyWidthConfig(caps, config) {
2026
+ return {
2027
+ ...caps,
2028
+ textSizing: config.privateUseWidth === 2,
2029
+ maybeWideEmojis: config.emojiWidth === 2
2030
+ };
2031
+ }
2032
+ //#endregion
2033
+ //#region packages/ag-term/src/device-attrs.ts
2034
+ /**
2035
+ * Device Attributes (DA1/DA2/DA3) + XTVERSION Queries
2036
+ *
2037
+ * Provides functions to query terminal identity and capabilities using
2038
+ * the standard VT device attribute escape sequences.
2039
+ *
2040
+ * Protocols:
2041
+ *
2042
+ * DA1 (Primary Device Attributes):
2043
+ * Query: CSI c (\x1b[c)
2044
+ * Response: CSI ? Ps ; Ps ; ... c
2045
+ *
2046
+ * DA2 (Secondary Device Attributes):
2047
+ * Query: CSI > c (\x1b[>c)
2048
+ * Response: CSI > Pt ; Pv ; Pc c
2049
+ * Where Pt=terminal type, Pv=firmware version, Pc=ROM cartridge id
2050
+ *
2051
+ * DA3 (Tertiary Device Attributes):
2052
+ * Query: CSI = c (\x1b[=c)
2053
+ * Response: DCS ! | hex-encoded-id ST (\x1bP!|{hex}\x1b\\)
2054
+ *
2055
+ * XTVERSION (Terminal Name + Version):
2056
+ * Query: CSI > 0 q (\x1b[>0q)
2057
+ * Response: DCS > | name(version) ST (\x1bP>|{text}\x1b\\)
2058
+ *
2059
+ * Supported by: xterm, Ghostty, Kitty, WezTerm, foot, VTE-based terminals
2060
+ */
2061
+ /** Regex for DA1 response: CSI ? params c */
2062
+ const DA1_RESPONSE_RE = /\x1b\[\?([\d;]+)c/;
2063
+ /** Regex for DA2 response: CSI > params c */
2064
+ const DA2_RESPONSE_RE = /\x1b\[>([\d;]+)c/;
2065
+ /** Regex for DA3 response: DCS ! | hex ST */
2066
+ const DA3_RESPONSE_RE = /\x1bP!\|([0-9a-fA-F]*)\x1b\\/;
2067
+ /** Regex for XTVERSION response: DCS > | text ST */
2068
+ const XTVERSION_RESPONSE_RE = /\x1bP>\|([^\x1b]*)\x1b\\/;
2069
+ /**
2070
+ * Query primary device attributes (DA1).
2071
+ *
2072
+ * Returns the list of attribute parameters the terminal reports.
2073
+ * Common params: 1=132-cols, 4=sixel, 6=selective-erase, 22=ANSI-color
2074
+ *
2075
+ * @param write Function to write to stdout
2076
+ * @param read Function to read a chunk from stdin
2077
+ * @param timeoutMs How long to wait for response (default: 200ms)
2078
+ */
2079
+ async function queryPrimaryDA(write, read, timeoutMs = 200) {
2080
+ write("\x1B[c");
2081
+ const data = await read(timeoutMs);
2082
+ if (data == null) return null;
2083
+ const match = DA1_RESPONSE_RE.exec(data);
2084
+ if (!match) return null;
2085
+ return { params: match[1].split(";").map((s) => parseInt(s, 10)) };
2086
+ }
2087
+ /**
2088
+ * Query secondary device attributes (DA2).
2089
+ *
2090
+ * Returns terminal type, firmware version, and ROM cartridge id.
2091
+ * Common type values: 0=VT100, 1=VT220, 41=xterm, 65=VT500
2092
+ *
2093
+ * @param write Function to write to stdout
2094
+ * @param read Function to read a chunk from stdin
2095
+ * @param timeoutMs How long to wait for response (default: 200ms)
2096
+ */
2097
+ async function querySecondaryDA(write, read, timeoutMs = 200) {
2098
+ write("\x1B[>c");
2099
+ const data = await read(timeoutMs);
2100
+ if (data == null) return null;
2101
+ const match = DA2_RESPONSE_RE.exec(data);
2102
+ if (!match) return null;
2103
+ const parts = match[1].split(";");
2104
+ if (parts.length < 3) return null;
2105
+ return {
2106
+ type: parseInt(parts[0], 10),
2107
+ version: parseInt(parts[1], 10),
2108
+ id: parseInt(parts[2], 10)
2109
+ };
2110
+ }
2111
+ /**
2112
+ * Query tertiary device attributes (DA3).
2113
+ *
2114
+ * Returns a hex-encoded unit ID string. Decode with Buffer.from(hex, 'hex').
2115
+ *
2116
+ * @param write Function to write to stdout
2117
+ * @param read Function to read a chunk from stdin
2118
+ * @param timeoutMs How long to wait for response (default: 200ms)
2119
+ */
2120
+ async function queryTertiaryDA(write, read, timeoutMs = 200) {
2121
+ write("\x1B[=c");
2122
+ const data = await read(timeoutMs);
2123
+ if (data == null) return null;
2124
+ const match = DA3_RESPONSE_RE.exec(data);
2125
+ if (!match) return null;
2126
+ return match[1];
2127
+ }
2128
+ /**
2129
+ * Query the terminal name and version via XTVERSION.
2130
+ *
2131
+ * Returns the version string as reported by the terminal, e.g.:
2132
+ * - "xterm(388)"
2133
+ * - "tmux 3.4"
2134
+ * - "WezTerm 20230712-072601-f4abf8fd"
2135
+ *
2136
+ * @param write Function to write to stdout
2137
+ * @param read Function to read a chunk from stdin
2138
+ * @param timeoutMs How long to wait for response (default: 200ms)
2139
+ */
2140
+ async function queryTerminalVersion(write, read, timeoutMs = 200) {
2141
+ write("\x1B[>0q");
2142
+ const data = await read(timeoutMs);
2143
+ if (data == null) return null;
2144
+ const match = XTVERSION_RESPONSE_RE.exec(data);
2145
+ if (!match) return null;
2146
+ return match[1];
2147
+ }
2148
+ /**
2149
+ * Query all device attributes: DA1, DA2, and XTVERSION.
2150
+ *
2151
+ * Convenience wrapper that queries all three sequentially.
2152
+ * DA3 is omitted from the combined query as it's rarely needed.
2153
+ *
2154
+ * @param stdout Writable stream (e.g., process.stdout)
2155
+ * @param stdin Readable stream (e.g., process.stdin)
2156
+ * @param timeoutMs Per-query timeout (default: 200ms)
2157
+ */
2158
+ async function queryDeviceAttributes(stdout, stdin, timeoutMs = 200) {
2159
+ const otherListeners = stdin.listenerCount("data") > 0;
2160
+ const wasRaw = stdin.isRaw;
2161
+ let didSetRaw = false;
2162
+ if (!wasRaw && !otherListeners) {
2163
+ stdin.setRawMode(true);
2164
+ didSetRaw = true;
2165
+ }
2166
+ try {
2167
+ const write = (s) => {
2168
+ stdout.write(s);
2169
+ };
2170
+ const read = (ms) => new Promise((resolve) => {
2171
+ const timer = setTimeout(() => {
2172
+ stdin.removeListener("data", onData);
2173
+ resolve(null);
2174
+ }, ms);
2175
+ function onData(chunk) {
2176
+ clearTimeout(timer);
2177
+ stdin.removeListener("data", onData);
2178
+ resolve(chunk.toString());
2179
+ }
2180
+ stdin.on("data", onData);
2181
+ });
2182
+ return {
2183
+ da1: await queryPrimaryDA(write, read, timeoutMs),
2184
+ da2: await querySecondaryDA(write, read, timeoutMs),
2185
+ version: await queryTerminalVersion(write, read, timeoutMs)
2186
+ };
2187
+ } finally {
2188
+ if (didSetRaw) stdin.setRawMode(false);
2189
+ }
2190
+ }
2191
+ //#endregion
2192
+ //#region packages/ag-term/src/mode-query.ts
2193
+ /**
2194
+ * DECRQM — DEC Private Mode Query
2195
+ *
2196
+ * Queries the terminal for the state of DEC private modes.
2197
+ *
2198
+ * Protocol:
2199
+ * - Query: CSI ? {mode} $ p
2200
+ * - Response: CSI ? {mode} ; {Ps} $ y
2201
+ *
2202
+ * Where Ps is:
2203
+ * 1 = set (mode is enabled)
2204
+ * 2 = reset (mode is disabled)
2205
+ * 0 = not recognized (unknown mode)
2206
+ * 3 = permanently set
2207
+ * 4 = permanently reset
2208
+ *
2209
+ * We normalize 3→"set" and 4→"reset" for simplicity.
2210
+ *
2211
+ * Supported by: xterm, Ghostty, Kitty, WezTerm, foot, VTE-based terminals
2212
+ */
2213
+ /** Regex for DECRPM response: CSI ? mode ; Ps $ y */
2214
+ const DECRPM_RESPONSE_RE = /\x1b\[\?(\d+);(\d+)\$y/;
2215
+ /** Well-known DEC private mode constants. */
2216
+ const DecMode = {
2217
+ CURSOR_VISIBLE: 25,
2218
+ ALT_SCREEN: 1049,
2219
+ MOUSE_TRACKING: 1e3,
2220
+ BRACKETED_PASTE: 2004,
2221
+ SYNC_OUTPUT: 2026,
2222
+ FOCUS_REPORTING: 1004
2223
+ };
2224
+ /**
2225
+ * Query the state of a single DEC private mode.
2226
+ *
2227
+ * @param write Function to write to stdout
2228
+ * @param read Function to read a chunk from stdin
2229
+ * @param mode DEC private mode number (e.g., DecMode.ALT_SCREEN)
2230
+ * @param timeoutMs How long to wait for response (default: 200ms)
2231
+ * @returns "set", "reset", or "unknown"
2232
+ */
2233
+ async function queryMode(write, read, mode, timeoutMs = 200) {
2234
+ write(`\x1b[?${mode}$p`);
2235
+ const data = await read(timeoutMs);
2236
+ if (data == null) return "unknown";
2237
+ const match = DECRPM_RESPONSE_RE.exec(data);
2238
+ if (!match) return "unknown";
2239
+ if (parseInt(match[1], 10) !== mode) return "unknown";
2240
+ switch (parseInt(match[2], 10)) {
2241
+ case 1:
2242
+ case 3: return "set";
2243
+ case 2:
2244
+ case 4: return "reset";
2245
+ default: return "unknown";
2246
+ }
2247
+ }
2248
+ /**
2249
+ * Query the state of multiple DEC private modes.
2250
+ *
2251
+ * Queries each mode sequentially and returns a Map of results.
2252
+ *
2253
+ * @param write Function to write to stdout
2254
+ * @param read Function to read a chunk from stdin
2255
+ * @param modes Array of DEC private mode numbers
2256
+ * @param timeoutMs Per-query timeout (default: 200ms)
2257
+ */
2258
+ async function queryModes(write, read, modes, timeoutMs = 200) {
2259
+ const results = /* @__PURE__ */ new Map();
2260
+ for (const mode of modes) {
2261
+ const state = await queryMode(write, read, mode, timeoutMs);
2262
+ results.set(mode, state);
2263
+ }
2264
+ return results;
2265
+ }
2266
+ //#endregion
2267
+ //#region packages/ag-term/src/pixel-size.ts
2268
+ /**
2269
+ * CSI 14t/18t — Terminal Pixel and Text Area Size Queries
2270
+ *
2271
+ * Queries the terminal for window dimensions in pixels and characters.
2272
+ *
2273
+ * Protocols:
2274
+ *
2275
+ * Text Area Pixels (CSI 14t):
2276
+ * Query: CSI 14 t
2277
+ * Response: CSI 4 ; height ; width t
2278
+ *
2279
+ * Text Area Size in Characters (CSI 18t):
2280
+ * Query: CSI 18 t
2281
+ * Response: CSI 8 ; rows ; cols t
2282
+ *
2283
+ * Cell size can be derived by dividing pixel dimensions by character dimensions.
2284
+ *
2285
+ * Supported by: xterm, Ghostty, Kitty, WezTerm, foot, iTerm2
2286
+ */
2287
+ /** Regex for CSI 4 ; height ; width t (pixel size response) */
2288
+ const PIXEL_RESPONSE_RE = /\x1b\[4;(\d+);(\d+)t/;
2289
+ /** Regex for CSI 8 ; rows ; cols t (text area size response) */
2290
+ const TEXT_AREA_RESPONSE_RE = /\x1b\[8;(\d+);(\d+)t/;
2291
+ /**
2292
+ * Query the terminal text area size in pixels.
2293
+ *
2294
+ * @param write Function to write to stdout
2295
+ * @param read Function to read a chunk from stdin
2296
+ * @param timeoutMs How long to wait for response (default: 200ms)
2297
+ * @returns Width and height in pixels, or null on timeout/unsupported
2298
+ */
2299
+ async function queryTextAreaPixels(write, read, timeoutMs = 200) {
2300
+ write("\x1B[14t");
2301
+ const data = await read(timeoutMs);
2302
+ if (data == null) return null;
2303
+ const match = PIXEL_RESPONSE_RE.exec(data);
2304
+ if (!match) return null;
2305
+ return {
2306
+ height: parseInt(match[1], 10),
2307
+ width: parseInt(match[2], 10)
2308
+ };
2309
+ }
2310
+ /**
2311
+ * Query the terminal text area size in characters (rows x columns).
2312
+ *
2313
+ * @param write Function to write to stdout
2314
+ * @param read Function to read a chunk from stdin
2315
+ * @param timeoutMs How long to wait for response (default: 200ms)
2316
+ * @returns Rows and columns, or null on timeout/unsupported
2317
+ */
2318
+ async function queryTextAreaSize(write, read, timeoutMs = 200) {
2319
+ write("\x1B[18t");
2320
+ const data = await read(timeoutMs);
2321
+ if (data == null) return null;
2322
+ const match = TEXT_AREA_RESPONSE_RE.exec(data);
2323
+ if (!match) return null;
2324
+ return {
2325
+ rows: parseInt(match[1], 10),
2326
+ cols: parseInt(match[2], 10)
2327
+ };
2328
+ }
2329
+ /**
2330
+ * Query the terminal cell size in pixels by querying both pixel
2331
+ * dimensions and character dimensions, then dividing.
2332
+ *
2333
+ * @param write Function to write to stdout
2334
+ * @param read Function to read a chunk from stdin
2335
+ * @param timeoutMs Per-query timeout (default: 200ms)
2336
+ * @returns Cell width and height in pixels, or null if either query fails
2337
+ */
2338
+ async function queryCellSize(write, read, timeoutMs = 200) {
2339
+ const pixels = await queryTextAreaPixels(write, read, timeoutMs);
2340
+ if (pixels == null) return null;
2341
+ const size = await queryTextAreaSize(write, read, timeoutMs);
2342
+ if (size == null) return null;
2343
+ if (size.cols === 0 || size.rows === 0) return null;
2344
+ return {
2345
+ width: pixels.width / size.cols,
2346
+ height: pixels.height / size.rows
2347
+ };
2348
+ }
2349
+ //#endregion
2350
+ //#region packages/ag-term/src/term-def.ts
2351
+ /**
2352
+ * Default dimensions when not detectable.
2353
+ */
2354
+ const DEFAULT_WIDTH = 80;
2355
+ const DEFAULT_HEIGHT = 24;
2356
+ /**
2357
+ * Check if a value is a Term instance (duck typing).
2358
+ *
2359
+ * Post km-silvery.caps-restructure Phase 7: the legacy hasCursor/hasInput/
2360
+ * hasColor methods were removed — caps/profile/write/size are the canonical
2361
+ * Term surface.
2362
+ */
2363
+ function isTerm(value) {
2364
+ if (!value || typeof value !== "object" && typeof value !== "function") return false;
2365
+ const obj = value;
2366
+ return typeof obj.caps === "object" && obj.caps !== null && typeof obj.write === "function" && typeof obj.size === "object";
2367
+ }
2368
+ /**
2369
+ * Check if a value is a TermDef (not a Term).
2370
+ */
2371
+ function isTermDef(value) {
2372
+ if (!value || typeof value !== "object") return false;
2373
+ return value.caps === void 0;
2374
+ }
2375
+ /**
2376
+ * Resolve a TermDef into concrete values.
2377
+ *
2378
+ * @param def - TermDef to resolve
2379
+ * @returns Resolved values ready for rendering
2380
+ */
2381
+ function resolveTermDef(def) {
2382
+ const width = def.width ?? def.stdout?.columns ?? DEFAULT_WIDTH;
2383
+ const height = def.height ?? def.stdout?.rows ?? DEFAULT_HEIGHT;
2384
+ let colors;
2385
+ if (def.colors === true) colors = detectColorLevel(def.stdout);
2386
+ else if (def.colors === false || def.colors === null) colors = "mono";
2387
+ else if (def.colors) colors = def.colors;
2388
+ else colors = detectColorLevel(def.stdout);
2389
+ let events = null;
2390
+ if (def.events) events = def.events;
2391
+ else if (def.stdin) events = createInputEvents(def.stdin);
2392
+ return {
2393
+ stdout: def.stdout ?? null,
2394
+ width,
2395
+ height,
2396
+ colors,
2397
+ events,
2398
+ isStatic: events === null
2399
+ };
2400
+ }
2401
+ /**
2402
+ * Resolve a Term instance into ResolvedTermDef.
2403
+ *
2404
+ * @param term - Term instance
2405
+ * @returns Resolved values
2406
+ */
2407
+ function resolveFromTerm(term) {
2408
+ const { stdin, stdout } = getInternalStreams(term);
2409
+ return {
2410
+ stdout,
2411
+ width: term.cols ?? DEFAULT_WIDTH,
2412
+ height: term.rows ?? DEFAULT_HEIGHT,
2413
+ colors: term.profile.colorLevel,
2414
+ events: createInputEvents(stdin),
2415
+ isStatic: false
2416
+ };
2417
+ }
2418
+ /**
2419
+ * Detect the color tier from a stdout stream.
2420
+ *
2421
+ * Phase 3 of km-silvery.terminal-profile-plateau: this is now a single-line
2422
+ * delegate into `createTerminalProfile` — no more local detection copy.
2423
+ */
2424
+ function detectColorLevel(stdout) {
2425
+ return createTerminalProfile({ stdout: stdout ?? { isTTY: false } }).colorLevel;
2426
+ }
2427
+ /**
2428
+ * Create an async iterable of input events from a stdin stream.
2429
+ *
2430
+ * This enables interactive mode by providing a source of keyboard events.
2431
+ */
2432
+ function createInputEvents(stdin) {
2433
+ return { [Symbol.asyncIterator]() {
2434
+ const buffer = [];
2435
+ let resolveNext = null;
2436
+ let done = false;
2437
+ const handleData = (chunk) => {
2438
+ const data = typeof chunk === "string" ? chunk : chunk.toString("utf8");
2439
+ for (const char of data) {
2440
+ const event = {
2441
+ type: "key",
2442
+ key: char,
2443
+ ctrl: char.charCodeAt(0) < 32 && char !== "\r" && char !== "\n" && char !== " "
2444
+ };
2445
+ if (resolveNext) {
2446
+ resolveNext({
2447
+ value: event,
2448
+ done: false
2449
+ });
2450
+ resolveNext = null;
2451
+ } else buffer.push(event);
2452
+ }
2453
+ };
2454
+ const handleEnd = () => {
2455
+ done = true;
2456
+ if (resolveNext) {
2457
+ resolveNext({
2458
+ value: void 0,
2459
+ done: true
2460
+ });
2461
+ resolveNext = null;
2462
+ }
2463
+ };
2464
+ if (stdin.isTTY && typeof stdin.setRawMode === "function") {
2465
+ stdin.setEncoding("utf8");
2466
+ stdin.on("data", handleData);
2467
+ stdin.on("end", handleEnd);
2468
+ }
2469
+ return {
2470
+ next() {
2471
+ const buffered = buffer.shift();
2472
+ if (buffered) return Promise.resolve({
2473
+ value: buffered,
2474
+ done: false
2475
+ });
2476
+ if (done) return Promise.resolve({
2477
+ value: void 0,
2478
+ done: true
2479
+ });
2480
+ return new Promise((resolve) => {
2481
+ resolveNext = resolve;
2482
+ });
2483
+ },
2484
+ return() {
2485
+ done = true;
2486
+ stdin.off("data", handleData);
2487
+ stdin.off("end", handleEnd);
2488
+ return Promise.resolve({
2489
+ value: void 0,
2490
+ done: true
2491
+ });
2492
+ }
2493
+ };
2494
+ } };
2495
+ }
2496
+ //#endregion
2497
+ //#region packages/ag-term/src/hit-registry-core.ts
2498
+ /**
2499
+ * Registry for managing hit regions.
2500
+ *
2501
+ * Components register their screen regions with targets, and the registry
2502
+ * resolves mouse clicks to the appropriate target based on position and z-index.
2503
+ *
2504
+ * @example
2505
+ * ```typescript
2506
+ * const registry = new HitRegistry();
2507
+ *
2508
+ * // Register a card region
2509
+ * registry.register('card-1', {
2510
+ * x: 10, y: 5, width: 30, height: 8,
2511
+ * target: { type: 'node', nodeId: 'abc123' },
2512
+ * zIndex: 10
2513
+ * });
2514
+ *
2515
+ * // Hit test a click
2516
+ * const target = registry.hitTest(15, 7);
2517
+ * // Returns { type: 'node', nodeId: 'abc123' }
2518
+ * ```
2519
+ */
2520
+ var HitRegistry = class {
2521
+ regions = /* @__PURE__ */ new Map();
2522
+ /**
2523
+ * Register a hit region with a unique ID.
2524
+ *
2525
+ * @param id - Unique identifier for the region (used for unregistration)
2526
+ * @param region - The region definition including position, size, target, and z-index
2527
+ */
2528
+ register(id, region) {
2529
+ this.regions.set(id, region);
2530
+ }
2531
+ /**
2532
+ * Unregister a hit region by ID.
2533
+ *
2534
+ * @param id - The ID used when registering the region
2535
+ */
2536
+ unregister(id) {
2537
+ this.regions.delete(id);
2538
+ }
2539
+ /**
2540
+ * Clear all registered regions.
2541
+ * Useful when the UI is completely redrawn.
2542
+ */
2543
+ clear() {
2544
+ this.regions.clear();
2545
+ }
2546
+ /**
2547
+ * Get the number of registered regions.
2548
+ * Useful for debugging.
2549
+ */
2550
+ get size() {
2551
+ return this.regions.size;
2552
+ }
2553
+ /**
2554
+ * Test a screen position and return the highest z-index matching target.
2555
+ *
2556
+ * @param screenX - X position on screen (0-indexed column)
2557
+ * @param screenY - Y position on screen (0-indexed row)
2558
+ * @returns The target of the highest z-index region containing the point, or null if none
2559
+ */
2560
+ hitTest(screenX, screenY) {
2561
+ let bestMatch = null;
2562
+ for (const region of this.regions.values()) if (screenX >= region.x && screenX < region.x + region.width && screenY >= region.y && screenY < region.y + region.height) {
2563
+ if (!bestMatch || region.zIndex > bestMatch.zIndex) bestMatch = region;
2564
+ }
2565
+ return bestMatch?.target ?? null;
2566
+ }
2567
+ /**
2568
+ * Get all regions that contain a point, sorted by z-index (highest first).
2569
+ * Useful for debugging or when you need to know all overlapping elements.
2570
+ *
2571
+ * @param screenX - X position on screen (0-indexed column)
2572
+ * @param screenY - Y position on screen (0-indexed row)
2573
+ * @returns Array of matching regions, sorted by z-index descending
2574
+ */
2575
+ hitTestAll(screenX, screenY) {
2576
+ const matches = [];
2577
+ for (const region of this.regions.values()) if (screenX >= region.x && screenX < region.x + region.width && screenY >= region.y && screenY < region.y + region.height) matches.push(region);
2578
+ return matches.sort((a, b) => b.zIndex - a.zIndex);
2579
+ }
2580
+ /**
2581
+ * Debug helper: get all registered regions.
2582
+ */
2583
+ getAllRegions() {
2584
+ return new Map(this.regions);
2585
+ }
2586
+ };
2587
+ /**
2588
+ * Generate a unique ID for hit region registration.
2589
+ */
2590
+ let hitRegionIdCounter = 0;
2591
+ function generateHitRegionId() {
2592
+ return `hit-${++hitRegionIdCounter}`;
2593
+ }
2594
+ /**
2595
+ * Reset the ID counter (useful for testing).
2596
+ */
2597
+ function resetHitRegionIdCounter() {
2598
+ hitRegionIdCounter = 0;
2599
+ }
2600
+ /**
2601
+ * Recommended z-index values for different UI layers.
2602
+ */
2603
+ const Z_INDEX = {
2604
+ BACKGROUND: 0,
2605
+ COLUMN_HEADER: 5,
2606
+ CARD: 10,
2607
+ FOLD_TOGGLE: 15,
2608
+ LINK: 20,
2609
+ FLOATING: 50,
2610
+ DIALOG: 100,
2611
+ DROPDOWN: 150,
2612
+ TOOLTIP: 200
2613
+ };
2614
+ //#endregion
2615
+ //#region packages/ag-term/src/non-tty.ts
2616
+ /**
2617
+ * Non-TTY Mode Support for Silvery
2618
+ *
2619
+ * Provides detection and rendering modes for non-interactive environments:
2620
+ * - Piped output (process.stdout.isTTY === false)
2621
+ * - CI environments
2622
+ * - TERM=dumb
2623
+ *
2624
+ * When in non-TTY mode, silvery avoids cursor positioning codes that garble
2625
+ * output in non-interactive environments.
2626
+ *
2627
+ * Modes:
2628
+ * - 'auto': Detect based on environment (default)
2629
+ * - 'tty': Force TTY mode (normal cursor positioning)
2630
+ * - 'line-by-line': Simple newline-separated output, no cursor movement
2631
+ * - 'static': Single output at end (no updates)
2632
+ * - 'plain': Strip all ANSI codes
2633
+ */
2634
+ /**
2635
+ * Check if the environment is a TTY.
2636
+ *
2637
+ * Returns false if:
2638
+ * - stdout.isTTY is false or undefined
2639
+ * - TERM=dumb
2640
+ * - CI environment variables are set
2641
+ *
2642
+ * Pass `emulator` (from `term.emulator` or a TerminalEmulator fixture) when
2643
+ * available to avoid a redundant env read. Without it, the TERM=dumb check
2644
+ * delegates to {@link createTerminalProfile} — the canonical single-source-
2645
+ * of-truth entry in `@silvery/ansi/profile`. CI env vars remain read
2646
+ * directly because they are orthogonal to terminal capabilities (a CI
2647
+ * runner's TTY status doesn't describe what the terminal can render).
2648
+ */
2649
+ function isTTY(stdout = process.stdout, emulator) {
2650
+ if (!stdout.isTTY) return false;
2651
+ if ((emulator?.TERM ?? createTerminalProfile().emulator.TERM) === "dumb") return false;
2652
+ if (process.env.CI || process.env.GITHUB_ACTIONS || process.env.GITLAB_CI || process.env.JENKINS_URL || process.env.BUILDKITE || process.env.CIRCLECI || process.env.TRAVIS) return false;
2653
+ return true;
2654
+ }
2655
+ /**
2656
+ * Resolve the non-TTY mode based on options and environment.
2657
+ *
2658
+ * When mode is 'auto':
2659
+ * - If TTY detected: returns 'tty'
2660
+ * - If non-TTY detected: returns 'line-by-line'
2661
+ */
2662
+ function resolveNonTTYMode(options = {}) {
2663
+ const { mode = "auto", stdout = process.stdout } = options;
2664
+ if (mode !== "auto") return mode;
2665
+ return isTTY(stdout) ? "tty" : "line-by-line";
2666
+ }
2667
+ /**
2668
+ * Convert buffer output to line-by-line format.
2669
+ *
2670
+ * Instead of using cursor positioning, outputs each line with a simple
2671
+ * carriage return and clear-to-end-of-line sequence.
2672
+ *
2673
+ * @param content The rendered content (may contain ANSI codes but no cursor positioning)
2674
+ * @param prevLineCount Number of lines in the previous frame (for clearing)
2675
+ * @returns Output string suitable for non-TTY rendering
2676
+ */
2677
+ function toLineByLineOutput(content, prevLineCount) {
2678
+ const lines = content.split("\n");
2679
+ let output = "";
2680
+ if (prevLineCount > 0) {
2681
+ output += "\r";
2682
+ if (prevLineCount > 1) output += `\x1b[${prevLineCount - 1}A`;
2683
+ }
2684
+ for (let i = 0; i < lines.length; i++) {
2685
+ if (i > 0) output += "\n";
2686
+ output += lines[i];
2687
+ output += "\x1B[K";
2688
+ }
2689
+ const extraLines = prevLineCount - lines.length;
2690
+ if (extraLines > 0) {
2691
+ for (let i = 0; i < extraLines; i++) output += "\n\x1B[K";
2692
+ output += `\x1b[${extraLines}A`;
2693
+ }
2694
+ return output;
2695
+ }
2696
+ /**
2697
+ * Convert buffer output to plain text format.
2698
+ *
2699
+ * Strips all ANSI codes and outputs simple newline-separated text.
2700
+ * No cursor movement or clearing.
2701
+ *
2702
+ * @param content The rendered content
2703
+ * @param prevLineCount Number of lines in the previous frame (unused in plain mode)
2704
+ * @returns Plain text output
2705
+ */
2706
+ function toPlainOutput(content, _prevLineCount) {
2707
+ const lines = stripAnsi(content).split("\n").map((line) => line.trimEnd());
2708
+ while (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
2709
+ return lines.join("\n");
2710
+ }
2711
+ /**
2712
+ * Create an output transformer based on the non-TTY mode.
2713
+ *
2714
+ * @param mode The resolved non-TTY mode
2715
+ * @returns A function that transforms output based on the mode
2716
+ */
2717
+ function createOutputTransformer(mode) {
2718
+ switch (mode) {
2719
+ case "tty": return (content) => content;
2720
+ case "line-by-line": return toLineByLineOutput;
2721
+ case "static": return () => "";
2722
+ case "plain": return toPlainOutput;
2723
+ }
2724
+ }
2725
+ /**
2726
+ * Count the number of lines in a string.
2727
+ */
2728
+ function countLines(str) {
2729
+ if (!str) return 0;
2730
+ return str.split("\n").length;
2731
+ }
2732
+ //#endregion
2733
+ //#region packages/ag-term/src/inspector.ts
2734
+ let inspectorEnabled = false;
2735
+ let inspectorOutput = process.stderr;
2736
+ /** Enable the silvery inspector. */
2737
+ function enableInspector(options) {
2738
+ inspectorEnabled = true;
2739
+ if (options?.logFile) inspectorOutput = process.getBuiltinModule("node:fs").createWriteStream(options.logFile, { flags: "a" });
2740
+ else if (options?.output) inspectorOutput = options.output;
2741
+ else inspectorOutput = process.stderr;
2742
+ }
2743
+ /** Disable the inspector. */
2744
+ function disableInspector() {
2745
+ inspectorEnabled = false;
2746
+ }
2747
+ /** Check if inspector is active. */
2748
+ function isInspectorEnabled() {
2749
+ return inspectorEnabled;
2750
+ }
2751
+ /**
2752
+ * Log render stats after each frame.
2753
+ *
2754
+ * Called by the scheduler after doRender completes. When the inspector
2755
+ * is disabled this is a no-op (zero overhead).
2756
+ */
2757
+ function inspectFrame(stats) {
2758
+ if (!inspectorEnabled) return;
2759
+ const line = `[silvery] frame #${stats.renderCount} ${stats.lastRenderTime.toFixed(1)}ms avg=${stats.avgRenderTime.toFixed(1)}ms skipped=${stats.skippedCount}\n`;
2760
+ inspectorOutput.write(line);
2761
+ }
2762
+ /**
2763
+ * Dump the component tree structure as indented text.
2764
+ *
2765
+ * Walks the SilveryNode tree and formats each node with its type, testID,
2766
+ * layout rect, and dirty flags.
2767
+ */
2768
+ function inspectTree(rootNode, options) {
2769
+ const maxDepth = options?.depth ?? 10;
2770
+ const showLayout = options?.showLayout ?? true;
2771
+ const lines = [];
2772
+ function walk(node, indent) {
2773
+ if (indent > maxDepth) return;
2774
+ const prefix = " ".repeat(indent);
2775
+ const type = node.type;
2776
+ const testID = node.props?.testID;
2777
+ const idStr = testID ? ` #${testID}` : "";
2778
+ let rectStr = "";
2779
+ if (showLayout) {
2780
+ if (node.boxRect) {
2781
+ const r = node.boxRect;
2782
+ rectStr = ` [${r.x},${r.y} ${r.width}x${r.height}]`;
2783
+ } else if (node.layoutNode) {
2784
+ const ln = node.layoutNode;
2785
+ rectStr = ` [${ln.getComputedLeft()},${ln.getComputedTop()} ${ln.getComputedWidth()}x${ln.getComputedHeight()}]`;
2786
+ }
2787
+ }
2788
+ const dirtyFlags = [];
2789
+ if (node.layoutNode?.isDirty()) dirtyFlags.push("layout");
2790
+ if (isDirty(node.dirtyBits, node.dirtyEpoch, 1)) dirtyFlags.push("content");
2791
+ if (isDirty(node.dirtyBits, node.dirtyEpoch, 2)) dirtyFlags.push("paint");
2792
+ if (isDirty(node.dirtyBits, node.dirtyEpoch, 4)) dirtyFlags.push("bg");
2793
+ if (isDirty(node.dirtyBits, node.dirtyEpoch, 16)) dirtyFlags.push("subtree");
2794
+ if (isDirty(node.dirtyBits, node.dirtyEpoch, 8)) dirtyFlags.push("children");
2795
+ const dirtyStr = dirtyFlags.length > 0 ? ` dirty=[${dirtyFlags.join(",")}]` : "";
2796
+ const textStr = node.textContent ? ` "${node.textContent.slice(0, 30)}${node.textContent.length > 30 ? "..." : ""}"` : "";
2797
+ lines.push(`${prefix}${type}${idStr}${rectStr}${dirtyStr}${textStr}`);
2798
+ for (const child of node.children) walk(child, indent + 1);
2799
+ }
2800
+ walk(rootNode, 0);
2801
+ return lines.join("\n");
2802
+ }
2803
+ /**
2804
+ * Auto-enable if SILVERY_DEV=1 is set.
2805
+ *
2806
+ * Call this at startup to respect the environment variable convention.
2807
+ */
2808
+ function autoEnableInspector() {
2809
+ if (process.env.SILVERY_DEV === "1" || process.env.SILVERY_DEV === "true") {
2810
+ const logFile = process.env.SILVERY_DEV_LOG;
2811
+ enableInspector(logFile ? { logFile } : void 0);
2812
+ }
2813
+ }
2814
+ //#endregion
2815
+ //#region packages/ag-term/src/measurer.ts
2816
+ /**
2817
+ * Extend a Term with measurement capabilities.
2818
+ *
2819
+ * Creates a width measurer from the term's caps and adds measurement
2820
+ * methods (displayWidth, graphemeWidth, wrapText, etc.) to the term.
2821
+ */
2822
+ function withMeasurer(term) {
2823
+ const caps = term.caps;
2824
+ const measurer = createMeasurer(caps ?? {});
2825
+ return Object.create(term, {
2826
+ maybeWideEmojis: {
2827
+ get: () => measurer.maybeWideEmojis,
2828
+ enumerable: true
2829
+ },
2830
+ textSizing: {
2831
+ get: () => measurer.textSizing,
2832
+ enumerable: true
2833
+ },
2834
+ displayWidth: {
2835
+ value: measurer.displayWidth.bind(measurer),
2836
+ enumerable: true
2837
+ },
2838
+ displayWidthAnsi: {
2839
+ value: measurer.displayWidthAnsi.bind(measurer),
2840
+ enumerable: true
2841
+ },
2842
+ graphemeWidth: {
2843
+ value: measurer.graphemeWidth.bind(measurer),
2844
+ enumerable: true
2845
+ },
2846
+ wrapText: {
2847
+ value: measurer.wrapText.bind(measurer),
2848
+ enumerable: true
2849
+ },
2850
+ sliceByWidth: {
2851
+ value: measurer.sliceByWidth.bind(measurer),
2852
+ enumerable: true
2853
+ },
2854
+ sliceByWidthFromEnd: {
2855
+ value: measurer.sliceByWidthFromEnd.bind(measurer),
2856
+ enumerable: true
2857
+ }
2858
+ });
2859
+ }
2860
+ /**
2861
+ * Create a pipeline configuration from caps and/or measurer.
2862
+ *
2863
+ * This is the single factory for PipelineConfig -- use it instead of
2864
+ * manually constructing { measurer, outputPhaseFn }.
2865
+ *
2866
+ * @param options.caps - Terminal capabilities (for output phase SGR generation).
2867
+ * Reads `caps.maybeWideEmojis` (text-presentation emoji width) and
2868
+ * `caps.textSizing` for measurement.
2869
+ * @param options.measurer - Explicit measurer (if omitted, created from caps)
2870
+ */
2871
+ function createPipeline(options = {}) {
2872
+ const { caps, measurer: explicitMeasurer } = options;
2873
+ const measurer = explicitMeasurer ?? createMeasurer(caps ?? {});
2874
+ const outputPhaseFn = createOutputPhase(caps ? {
2875
+ underlineStyles: caps.underlineStyles,
2876
+ underlineColor: caps.underlineColor,
2877
+ overline: caps.overline,
2878
+ colorLevel: caps.colorLevel
2879
+ } : {}, measurer);
2880
+ if (caps?.colorLevel) setActiveColorLevel(caps.colorLevel);
2881
+ return {
2882
+ measurer,
2883
+ outputPhaseFn
2884
+ };
2885
+ }
2886
+ //#endregion
2887
+ //#region packages/ag-term/src/scroll-utils.ts
2888
+ /**
2889
+ * Scroll Utilities
2890
+ *
2891
+ * Shared functions for edge-based scrolling behavior across VirtualList,
2892
+ * HorizontalVirtualList, and other scroll-aware components.
2893
+ */
2894
+ /**
2895
+ * Calculate edge-based scroll offset.
2896
+ *
2897
+ * Only scrolls when cursor approaches the edge of the visible area.
2898
+ * This provides smoother scrolling by starting to scroll before hitting
2899
+ * the absolute edge, maintaining context around the selected item.
2900
+ *
2901
+ * ## Algorithm
2902
+ *
2903
+ * The viewport is divided into zones:
2904
+ * ```
2905
+ * |<padding>|<------ safe zone ------>|<padding>|
2906
+ * | scroll | no scroll needed | scroll |
2907
+ * | if < | | if > |
2908
+ * ```
2909
+ *
2910
+ * When the selected item enters a padding zone, the viewport scrolls
2911
+ * to keep the item visible with margin.
2912
+ *
2913
+ * ## Asymmetry Note
2914
+ *
2915
+ * The +1 in the "scroll down/right" case is intentional:
2916
+ * - Offset points to the TOP/LEFT of the viewport
2917
+ * - We want the selected item to be `padding` items from the BOTTOM/RIGHT
2918
+ * - Formula: `selectedIndex - visibleCount + padding + 1`
2919
+ *
2920
+ * Example: visibleCount=10, padding=2, selectedIndex=15
2921
+ * offset = 15 - 10 + 2 + 1 = 8
2922
+ * viewport shows items 8-17, selected item 15 is at position 7 (2 from bottom)
2923
+ *
2924
+ * @param selectedIndex - Currently selected item index
2925
+ * @param currentOffset - Current scroll offset (topmost/leftmost visible item)
2926
+ * @param visibleCount - Number of items visible in viewport
2927
+ * @param totalCount - Total number of items
2928
+ * @param padding - Items to keep visible before/after cursor (default: 1)
2929
+ * @returns New scroll offset
2930
+ */
2931
+ function calcEdgeBasedScrollOffset(selectedIndex, currentOffset, visibleCount, totalCount, padding = 1) {
2932
+ if (totalCount <= visibleCount) return 0;
2933
+ const effectivePadding = padding * 2 >= visibleCount ? 0 : padding;
2934
+ const visibleStart = currentOffset;
2935
+ const visibleEnd = currentOffset + visibleCount - 1;
2936
+ const paddedStart = visibleStart + effectivePadding;
2937
+ const paddedEnd = visibleEnd - effectivePadding;
2938
+ let newOffset = currentOffset;
2939
+ if (selectedIndex < paddedStart) newOffset = Math.max(0, selectedIndex - effectivePadding);
2940
+ else if (effectivePadding === 0 && selectedIndex === paddedStart && currentOffset > 0 && visibleCount > padding) newOffset = Math.max(0, selectedIndex - padding);
2941
+ else if (selectedIndex > paddedEnd) newOffset = Math.min(totalCount - visibleCount, selectedIndex - visibleCount + effectivePadding + 1);
2942
+ return Math.max(0, Math.min(newOffset, totalCount - visibleCount));
2943
+ }
2944
+ //#endregion
2945
+ //#region packages/ag-term/src/scroll-region.ts
2946
+ /**
2947
+ * Terminal scroll region (DECSTBM) utilities.
2948
+ *
2949
+ * Scroll regions tell the terminal to natively scroll content within
2950
+ * a defined row range, which is faster than re-rendering all cells.
2951
+ *
2952
+ * DECSTBM (DEC Set Top and Bottom Margins) is supported by most modern
2953
+ * terminal emulators: xterm, iTerm2, Kitty, Ghostty, WezTerm, etc.
2954
+ */
2955
+ const ESC = "\x1B";
2956
+ /** Set terminal scroll region (1-indexed top and bottom rows). */
2957
+ function setScrollRegion(stdout, top, bottom) {
2958
+ stdout.write(`${ESC}[${top};${bottom}r`);
2959
+ }
2960
+ /** Reset scroll region to full terminal. */
2961
+ function resetScrollRegion(stdout) {
2962
+ stdout.write(`${ESC}[r`);
2963
+ }
2964
+ /** Scroll content up by N lines within the current scroll region. */
2965
+ function scrollUp(stdout, lines = 1) {
2966
+ stdout.write(`${ESC}[${lines}S`);
2967
+ }
2968
+ /** Scroll content down by N lines within the current scroll region. */
2969
+ function scrollDown(stdout, lines = 1) {
2970
+ stdout.write(`${ESC}[${lines}T`);
2971
+ }
2972
+ /** Move cursor to a specific position (1-indexed row and column). */
2973
+ function moveCursor(stdout, row, col) {
2974
+ stdout.write(`${ESC}[${row};${col}H`);
2975
+ }
2976
+ /**
2977
+ * Detect if the terminal likely supports DECSTBM scroll regions.
2978
+ *
2979
+ * Most modern terminals do (xterm, iTerm2, Kitty, Ghostty, WezTerm, etc.)
2980
+ * but some (e.g., Linux console) may not handle them correctly.
2981
+ *
2982
+ * Pass `emulator` (via `term.emulator` or a test fixture) when in scope.
2983
+ * Without it, this falls back to {@link createTerminalProfile} — the canonical
2984
+ * single-source-of-truth entry point in `@silvery/ansi/profile`. Direct
2985
+ * reads of terminal-signal env vars (TERM / TERM_PROGRAM / …) are banned
2986
+ * outside that module — see `scripts/lint-env-reads.ts`.
2987
+ */
2988
+ function supportsScrollRegions(emulator) {
2989
+ const resolved = emulator ?? createTerminalProfile().emulator;
2990
+ const term = resolved.TERM;
2991
+ const termProgram = resolved.program;
2992
+ if (termProgram === "iTerm.app" || termProgram === "WezTerm" || termProgram === "ghostty" || termProgram === "Ghostty" || termProgram === "vscode") return true;
2993
+ if (term.includes("xterm") || term.includes("screen") || term.includes("tmux") || term.includes("kitty")) return true;
2994
+ if (term === "linux") return false;
2995
+ return term !== "";
2996
+ }
2997
+ //#endregion
2998
+ //#region packages/ag-term/src/pane-manager.ts
2999
+ /** Create a single-pane layout */
3000
+ function createLeaf(id) {
3001
+ return {
3002
+ type: "leaf",
3003
+ id
3004
+ };
3005
+ }
3006
+ /** Get all leaf pane IDs in depth-first left-to-right order */
3007
+ function getPaneIds(layout) {
3008
+ if (layout.type === "leaf") return [layout.id];
3009
+ return [...getPaneIds(layout.first), ...getPaneIds(layout.second)];
3010
+ }
3011
+ /** Find the next/previous pane in tab order (depth-first left-to-right) */
3012
+ function getTabOrder(layout) {
3013
+ return getPaneIds(layout);
3014
+ }
3015
+ /**
3016
+ * Split a pane into two. Returns new layout tree with the target pane split.
3017
+ * The original pane becomes the first child; the new pane becomes the second.
3018
+ */
3019
+ function splitPane(layout, targetPaneId, direction, newPaneId, ratio = .5) {
3020
+ const clampedRatio = clampRatio(ratio);
3021
+ if (layout.type === "leaf") {
3022
+ if (layout.id === targetPaneId) return {
3023
+ type: "split",
3024
+ direction,
3025
+ ratio: clampedRatio,
3026
+ first: {
3027
+ type: "leaf",
3028
+ id: targetPaneId
3029
+ },
3030
+ second: {
3031
+ type: "leaf",
3032
+ id: newPaneId
3033
+ }
3034
+ };
3035
+ return layout;
3036
+ }
3037
+ const newFirst = splitPane(layout.first, targetPaneId, direction, newPaneId, ratio);
3038
+ const newSecond = splitPane(layout.second, targetPaneId, direction, newPaneId, ratio);
3039
+ if (newFirst === layout.first && newSecond === layout.second) return layout;
3040
+ return {
3041
+ ...layout,
3042
+ first: newFirst,
3043
+ second: newSecond
3044
+ };
3045
+ }
3046
+ /**
3047
+ * Remove a pane from the layout. The sibling takes the full space.
3048
+ * Returns null if the removed pane was the last one.
3049
+ */
3050
+ function removePane(layout, paneId) {
3051
+ if (layout.type === "leaf") return layout.id === paneId ? null : layout;
3052
+ if (layout.first.type === "leaf" && layout.first.id === paneId) return layout.second;
3053
+ if (layout.second.type === "leaf" && layout.second.id === paneId) return layout.first;
3054
+ const newFirst = removePane(layout.first, paneId);
3055
+ const newSecond = removePane(layout.second, paneId);
3056
+ if (newFirst === null) return newSecond;
3057
+ if (newSecond === null) return newFirst;
3058
+ if (newFirst === layout.first && newSecond === layout.second) return layout;
3059
+ return {
3060
+ ...layout,
3061
+ first: newFirst,
3062
+ second: newSecond
3063
+ };
3064
+ }
3065
+ /** Swap two panes' positions in the layout */
3066
+ function swapPanes(layout, paneId1, paneId2) {
3067
+ if (layout.type === "leaf") {
3068
+ if (layout.id === paneId1) return {
3069
+ type: "leaf",
3070
+ id: paneId2
3071
+ };
3072
+ if (layout.id === paneId2) return {
3073
+ type: "leaf",
3074
+ id: paneId1
3075
+ };
3076
+ return layout;
3077
+ }
3078
+ const newFirst = swapPanes(layout.first, paneId1, paneId2);
3079
+ const newSecond = swapPanes(layout.second, paneId1, paneId2);
3080
+ if (newFirst === layout.first && newSecond === layout.second) return layout;
3081
+ return {
3082
+ ...layout,
3083
+ first: newFirst,
3084
+ second: newSecond
3085
+ };
3086
+ }
3087
+ /**
3088
+ * Resize a split: adjust the ratio of the nearest ancestor split
3089
+ * that contains the target pane as its first child.
3090
+ * Positive delta = grow (first child gets more), negative = shrink.
3091
+ */
3092
+ function resizeSplit(layout, paneId, delta) {
3093
+ if (layout.type === "leaf") return layout;
3094
+ if (getPaneIds(layout.first).includes(paneId)) {
3095
+ const newRatio = clampRatio(layout.ratio + delta);
3096
+ if (newRatio === layout.ratio) return layout;
3097
+ return {
3098
+ ...layout,
3099
+ ratio: newRatio
3100
+ };
3101
+ }
3102
+ if (getPaneIds(layout.second).includes(paneId)) {
3103
+ const newRatio = clampRatio(layout.ratio - delta);
3104
+ if (newRatio === layout.ratio) return layout;
3105
+ return {
3106
+ ...layout,
3107
+ ratio: newRatio
3108
+ };
3109
+ }
3110
+ return layout;
3111
+ }
3112
+ /**
3113
+ * Find the pane adjacent to the given pane in a direction.
3114
+ *
3115
+ * For left/right: looks for siblings in horizontal splits.
3116
+ * For up/down: looks for siblings in vertical splits.
3117
+ *
3118
+ * Returns null if no adjacent pane exists in that direction.
3119
+ */
3120
+ function findAdjacentPane(layout, paneId, direction) {
3121
+ const path = findPath(layout, paneId);
3122
+ if (!path) return null;
3123
+ const splitDirection = direction === "left" || direction === "right" ? "horizontal" : "vertical";
3124
+ const goToSecond = direction === "right" || direction === "down";
3125
+ for (let i = path.length - 1; i >= 0; i--) {
3126
+ const step = path[i];
3127
+ if (step.node.type !== "split") continue;
3128
+ if (step.node.direction !== splitDirection) continue;
3129
+ if (step.side === "first" && goToSecond) return firstLeaf(step.node.second);
3130
+ if (step.side === "second" && !goToSecond) return lastLeaf(step.node.first);
3131
+ }
3132
+ return null;
3133
+ }
3134
+ function clampRatio(ratio) {
3135
+ return Math.max(.1, Math.min(.9, ratio));
3136
+ }
3137
+ /** Get the first (leftmost/topmost) leaf ID */
3138
+ function firstLeaf(node) {
3139
+ if (node.type === "leaf") return node.id;
3140
+ return firstLeaf(node.first);
3141
+ }
3142
+ /** Get the last (rightmost/bottommost) leaf ID */
3143
+ function lastLeaf(node) {
3144
+ if (node.type === "leaf") return node.id;
3145
+ return lastLeaf(node.second);
3146
+ }
3147
+ /** Find the path from root to a leaf, recording which side we took at each split */
3148
+ function findPath(layout, paneId) {
3149
+ if (layout.type === "leaf") return layout.id === paneId ? [] : null;
3150
+ const firstPath = findPath(layout.first, paneId);
3151
+ if (firstPath !== null) return [{
3152
+ node: layout,
3153
+ side: "first"
3154
+ }, ...firstPath];
3155
+ const secondPath = findPath(layout.second, paneId);
3156
+ if (secondPath !== null) return [{
3157
+ node: layout,
3158
+ side: "second"
3159
+ }, ...secondPath];
3160
+ return null;
3161
+ }
3162
+ //#endregion
3163
+ //#region packages/ag-term/src/selection-renderer.ts
3164
+ function normalizeSelectionColor(color) {
3165
+ if (color === void 0) return void 0;
3166
+ if (typeof color === "string") return parseColor(color);
3167
+ return color;
3168
+ }
3169
+ /**
3170
+ * Compute selection style changes for all cells within a selection range.
3171
+ *
3172
+ * Returns a sparse list of cell changes (fg/bg only). The caller applies these
3173
+ * to the buffer before the output phase diffs it. This approach:
3174
+ * - Composes correctly with existing cell styles
3175
+ * - Handles already-inverted content (swaps back to normal instead of double-inverting)
3176
+ * - Respects SELECTABLE_FLAG per cell (skip non-selectable)
3177
+ * - Works with the normal diff/output renderer (no separate ANSI pass)
3178
+ *
3179
+ * @param buffer The rendered buffer (post-render-phase)
3180
+ * @param selection Current selection range, or null
3181
+ * @param theme Optional selection theme colors
3182
+ * @param respectSelectableFlag When true, skip cells without SELECTABLE_FLAG
3183
+ */
3184
+ function composeSelectionCells(buffer, selection, theme, respectSelectableFlag = false, scope) {
3185
+ if (!selection) return [];
3186
+ const { startRow, startCol, endRow, endCol } = normalizeRange(selection);
3187
+ const changes = [];
3188
+ for (let row = startRow; row <= endRow; row++) {
3189
+ let colStart = row === startRow ? startCol : 0;
3190
+ let colEnd = row === endRow ? endCol : buffer.width - 1;
3191
+ const rowMeta = buffer.getRowMeta(row);
3192
+ if (rowMeta.lastContentCol >= 0) colEnd = Math.min(colEnd, rowMeta.lastContentCol);
3193
+ if (colStart > colEnd) continue;
3194
+ if (scope) {
3195
+ colStart = Math.max(colStart, scope.left);
3196
+ colEnd = Math.min(colEnd, scope.right);
3197
+ if (colStart > colEnd) continue;
3198
+ }
3199
+ const firstContentCol = findFirstContentCol(buffer, row, colStart, colEnd, respectSelectableFlag);
3200
+ if (firstContentCol === null) continue;
3201
+ colStart = Math.max(colStart, firstContentCol);
3202
+ for (let col = colStart; col <= colEnd; col++) {
3203
+ if (buffer.isCellContinuation(col, row)) continue;
3204
+ if (respectSelectableFlag && !buffer.isCellSelectable(col, row)) continue;
3205
+ const cellFg = buffer.getCellFg(col, row);
3206
+ const cellBg = buffer.getCellBg(col, row);
3207
+ let newFg;
3208
+ let newBg;
3209
+ let inverseAttr = false;
3210
+ if (theme?.selectionBg != null) {
3211
+ newFg = theme.selectionFg ?? cellFg;
3212
+ newBg = theme.selectionBg;
3213
+ } else if (cellFg == null || cellBg == null) {
3214
+ newFg = cellFg;
3215
+ newBg = cellBg;
3216
+ inverseAttr = true;
3217
+ } else {
3218
+ newFg = cellBg;
3219
+ newBg = cellFg;
3220
+ }
3221
+ changes.push({
3222
+ col,
3223
+ row,
3224
+ fg: newFg,
3225
+ bg: newBg,
3226
+ inverseAttr
3227
+ });
3228
+ }
3229
+ }
3230
+ return changes;
3231
+ }
3232
+ function findFirstContentCol(buffer, row, colStart, colEnd, respectSelectableFlag) {
3233
+ for (let col = colStart; col <= colEnd; col++) {
3234
+ if (buffer.isCellContinuation(col, row)) continue;
3235
+ if (respectSelectableFlag && !buffer.isCellSelectable(col, row)) continue;
3236
+ if (buffer.getCell(col, row).char.trim().length > 0) return col;
3237
+ }
3238
+ return null;
3239
+ }
3240
+ /**
3241
+ * Apply selection style changes to a buffer.
3242
+ *
3243
+ * Modifies the buffer in-place by setting fg/bg on affected cells.
3244
+ * Call this after the render phase and before the output phase.
3245
+ *
3246
+ * @param buffer The rendered buffer to modify
3247
+ * @param changes Cell changes from composeSelectionCells
3248
+ */
3249
+ function applySelectionToBuffer(buffer, changes) {
3250
+ for (const change of changes) {
3251
+ const cell = buffer.getCell(change.col, change.row);
3252
+ if (change.inverseAttr) buffer.setCell(change.col, change.row, {
3253
+ ...cell,
3254
+ attrs: {
3255
+ ...cell.attrs,
3256
+ inverse: !cell.attrs.inverse
3257
+ }
3258
+ });
3259
+ else buffer.setCell(change.col, change.row, {
3260
+ ...cell,
3261
+ fg: normalizeSelectionColor(change.fg) ?? null,
3262
+ bg: normalizeSelectionColor(change.bg) ?? null
3263
+ });
3264
+ }
3265
+ }
3266
+ /**
3267
+ * Compute search-highlight style changes for a list of highlight ranges.
3268
+ *
3269
+ * Returns a sparse list of cell changes (fg/bg only) — apply them with
3270
+ * `applySelectionToBuffer` (the apply step is style-source-agnostic — it
3271
+ * just sets fg/bg or toggles inverse on the cells you give it).
3272
+ *
3273
+ * Default behaviour mirrors the legacy `renderSearchHighlights` that wrote
3274
+ * `\x1b[7m...\x1b[27m` past the buffer: cells with default fg/bg get the
3275
+ * inverse-attr toggle (so the terminal handles the SGR 7 swap at display).
3276
+ * Cells with explicit fg/bg get a direct fg↔bg swap. A theme can override
3277
+ * with explicit `selectionFg` / `selectionBg` tokens.
3278
+ *
3279
+ * Tracking: km-silvery.delete-search-overlay-ansi
3280
+ */
3281
+ function composeSearchHighlightCells(buffer, highlights, theme) {
3282
+ if (highlights.length === 0) return [];
3283
+ const changes = [];
3284
+ for (const h of highlights) {
3285
+ const row = h.screenRow;
3286
+ if (row < 0 || row >= buffer.height) continue;
3287
+ const colStart = Math.max(0, h.startCol);
3288
+ const colEnd = Math.min(buffer.width - 1, h.endCol);
3289
+ if (colStart > colEnd) continue;
3290
+ for (let col = colStart; col <= colEnd; col++) {
3291
+ if (buffer.isCellContinuation(col, row)) continue;
3292
+ const cellFg = buffer.getCellFg(col, row);
3293
+ const cellBg = buffer.getCellBg(col, row);
3294
+ let newFg;
3295
+ let newBg;
3296
+ let inverseAttr = false;
3297
+ if (theme?.selectionBg != null) {
3298
+ newFg = theme.selectionFg ?? cellFg;
3299
+ newBg = theme.selectionBg;
3300
+ } else if (cellFg == null || cellBg == null) {
3301
+ newFg = cellFg;
3302
+ newBg = cellBg;
3303
+ inverseAttr = true;
3304
+ } else {
3305
+ newFg = cellBg;
3306
+ newBg = cellFg;
3307
+ }
3308
+ changes.push({
3309
+ col,
3310
+ row,
3311
+ fg: newFg,
3312
+ bg: newBg,
3313
+ inverseAttr
3314
+ });
3315
+ }
3316
+ }
3317
+ return changes;
3318
+ }
3319
+ //#endregion
3320
+ //#region packages/ag-term/src/virtual-scrollback.ts
3321
+ /**
3322
+ * Virtual scrollback buffer for storing historical rendered content.
3323
+ *
3324
+ * Used by virtual inline mode to maintain scrollable history while
3325
+ * rendering in altscreen (which normally has no scrollback).
3326
+ *
3327
+ * Implementation uses a circular buffer for O(1) push and bounded memory.
3328
+ */
3329
+ function createVirtualScrollback(options) {
3330
+ const maxLines = options?.maxLines ?? 1e4;
3331
+ const ansiLines = new Array(maxLines);
3332
+ const plainLines = new Array(maxLines);
3333
+ let head = 0;
3334
+ let count = 0;
3335
+ return {
3336
+ push(lines) {
3337
+ for (const line of lines) {
3338
+ ansiLines[head] = line;
3339
+ plainLines[head] = stripAnsi(line);
3340
+ head = (head + 1) % maxLines;
3341
+ if (count < maxLines) count++;
3342
+ }
3343
+ },
3344
+ getVisibleRows(offset, rowCount) {
3345
+ const result = [];
3346
+ for (let i = 0; i < rowCount; i++) {
3347
+ const logicalIndex = count - offset - rowCount + i;
3348
+ if (logicalIndex < 0 || logicalIndex >= count) result.push("");
3349
+ else {
3350
+ const physical = (head - count + logicalIndex + maxLines) % maxLines;
3351
+ result.push(ansiLines[physical]);
3352
+ }
3353
+ }
3354
+ return result;
3355
+ },
3356
+ get totalLines() {
3357
+ return count;
3358
+ },
3359
+ search(query) {
3360
+ if (!query) return [];
3361
+ const lowerQuery = query.toLowerCase();
3362
+ const matches = [];
3363
+ for (let i = 0; i < count; i++) if (plainLines[(head - count + i + maxLines) % maxLines].toLowerCase().includes(lowerQuery)) matches.push(i);
3364
+ return matches;
3365
+ },
3366
+ clear() {
3367
+ head = 0;
3368
+ count = 0;
3369
+ }
3370
+ };
3371
+ }
3372
+ //#endregion
3373
+ //#region packages/ag-term/src/history-buffer.ts
3374
+ /**
3375
+ * Ring buffer for frozen scrollback items.
3376
+ *
3377
+ * Each item represents a rendered list entry (card, row, etc.) with its
3378
+ * ANSI snapshot, split rows, and plain-text rows for searching.
3379
+ * When total rows or cached ANSI bytes exceed capacity, the oldest items are
3380
+ * evicted. The byte count tracks the serialized string payload; it is a
3381
+ * deterministic cache budget, not an exact process heap measurement.
3382
+ */
3383
+ function createHistoryItem(key, ansi, width) {
3384
+ const rows = ansi.split("\n");
3385
+ return {
3386
+ key,
3387
+ ansi,
3388
+ rows,
3389
+ plainTextRows: rows.map((r) => stripAnsi(r)),
3390
+ width
3391
+ };
3392
+ }
3393
+ function createHistoryBuffer(options = 1e4) {
3394
+ const capacity = typeof options === "number" ? options : options.capacityRows ?? 1e4;
3395
+ const capacityBytes = typeof options === "number" ? null : normalizeCapacityBytes(options.capacityBytes);
3396
+ let items = [];
3397
+ let _totalRows = 0;
3398
+ let _totalBytes = 0;
3399
+ function evict() {
3400
+ while (items.length > 0 && (_totalRows > capacity || capacityBytes !== null && _totalBytes > capacityBytes)) {
3401
+ const removed = items.shift();
3402
+ _totalRows -= removed.rows.length;
3403
+ _totalBytes -= estimateHistoryItemBytes(removed);
3404
+ }
3405
+ }
3406
+ /** Walk items to find which item contains the given document row. */
3407
+ function resolveRow(row) {
3408
+ if (row < 0 || row >= _totalRows) return null;
3409
+ let cumulative = 0;
3410
+ for (let i = 0; i < items.length; i++) {
3411
+ const itemRows = items[i].rows.length;
3412
+ if (row < cumulative + itemRows) return {
3413
+ itemIndex: i,
3414
+ localRow: row - cumulative
3415
+ };
3416
+ cumulative += itemRows;
3417
+ }
3418
+ return null;
3419
+ }
3420
+ return {
3421
+ push(item) {
3422
+ items.push(item);
3423
+ _totalRows += item.rows.length;
3424
+ _totalBytes += estimateHistoryItemBytes(item);
3425
+ evict();
3426
+ },
3427
+ get totalRows() {
3428
+ return _totalRows;
3429
+ },
3430
+ get totalBytes() {
3431
+ return _totalBytes;
3432
+ },
3433
+ get itemCount() {
3434
+ return items.length;
3435
+ },
3436
+ get capacity() {
3437
+ return capacity;
3438
+ },
3439
+ get capacityBytes() {
3440
+ return capacityBytes;
3441
+ },
3442
+ getRows(startRow, count) {
3443
+ const result = [];
3444
+ for (let r = startRow; r < startRow + count; r++) {
3445
+ const resolved = resolveRow(r);
3446
+ if (resolved) result.push(items[resolved.itemIndex].rows[resolved.localRow]);
3447
+ else result.push("");
3448
+ }
3449
+ return result;
3450
+ },
3451
+ getPlainTextRows(startRow, count) {
3452
+ const result = [];
3453
+ for (let r = startRow; r < startRow + count; r++) {
3454
+ const resolved = resolveRow(r);
3455
+ if (resolved) result.push(items[resolved.itemIndex].plainTextRows[resolved.localRow]);
3456
+ else result.push("");
3457
+ }
3458
+ return result;
3459
+ },
3460
+ search(query) {
3461
+ if (!query) return [];
3462
+ const lowerQuery = query.toLowerCase();
3463
+ const matches = [];
3464
+ let rowOffset = 0;
3465
+ for (const item of items) {
3466
+ for (let r = 0; r < item.plainTextRows.length; r++) if (item.plainTextRows[r].toLowerCase().includes(lowerQuery)) matches.push(rowOffset + r);
3467
+ rowOffset += item.rows.length;
3468
+ }
3469
+ return matches;
3470
+ },
3471
+ getItemAtRow(row) {
3472
+ const resolved = resolveRow(row);
3473
+ if (!resolved) return null;
3474
+ return {
3475
+ item: items[resolved.itemIndex],
3476
+ localRow: resolved.localRow
3477
+ };
3478
+ },
3479
+ clear() {
3480
+ items = [];
3481
+ _totalRows = 0;
3482
+ _totalBytes = 0;
3483
+ }
3484
+ };
3485
+ }
3486
+ function normalizeCapacityBytes(value) {
3487
+ return Number.isFinite(value) && value !== void 0 && value > 0 ? Math.floor(value) : null;
3488
+ }
3489
+ const utf8Encoder = new TextEncoder();
3490
+ function estimateHistoryItemBytes(item) {
3491
+ return utf8Encoder.encode(item.ansi).byteLength;
3492
+ }
3493
+ //#endregion
3494
+ //#region packages/ag-term/src/viewport-compositor.ts
3495
+ function composeViewport(config) {
3496
+ const { history, viewportHeight, scrollOffset } = config;
3497
+ const totalHistory = history.totalRows;
3498
+ if (scrollOffset <= 0 || totalHistory === 0) return {
3499
+ overlayRows: [],
3500
+ overlayRowCount: 0,
3501
+ liveRowsVisible: viewportHeight,
3502
+ isScrolledUp: false,
3503
+ totalHeight: totalHistory + viewportHeight
3504
+ };
3505
+ const startRow = Math.max(0, totalHistory - Math.min(scrollOffset, totalHistory));
3506
+ const rowsToShow = Math.min(viewportHeight, totalHistory - startRow);
3507
+ return {
3508
+ overlayRows: history.getRows(startRow, rowsToShow),
3509
+ overlayRowCount: rowsToShow,
3510
+ liveRowsVisible: viewportHeight - rowsToShow,
3511
+ isScrolledUp: true,
3512
+ totalHeight: totalHistory + viewportHeight
3513
+ };
3514
+ }
3515
+ //#endregion
3516
+ //#region packages/ag-term/src/list-document.ts
3517
+ function createListDocument(history, getLiveItems) {
3518
+ function liveRowCount() {
3519
+ let total = 0;
3520
+ for (const block of getLiveItems()) total += block.rows.length;
3521
+ return total;
3522
+ }
3523
+ return {
3524
+ get totalRows() {
3525
+ return history.totalRows + liveRowCount();
3526
+ },
3527
+ get frozenRows() {
3528
+ return history.totalRows;
3529
+ },
3530
+ get liveRows() {
3531
+ return liveRowCount();
3532
+ },
3533
+ getRows(startRow, count) {
3534
+ const frozen = history.totalRows;
3535
+ const result = [];
3536
+ for (let r = startRow; r < startRow + count; r++) if (r < 0 || r >= this.totalRows) result.push("");
3537
+ else if (r < frozen) result.push(...history.getRows(r, 1));
3538
+ else {
3539
+ let liveRow = r - frozen;
3540
+ let found = false;
3541
+ for (const block of getLiveItems()) {
3542
+ if (liveRow < block.rows.length) {
3543
+ result.push(block.rows[liveRow]);
3544
+ found = true;
3545
+ break;
3546
+ }
3547
+ liveRow -= block.rows.length;
3548
+ }
3549
+ if (!found) result.push("");
3550
+ }
3551
+ return result;
3552
+ },
3553
+ getPlainTextRows(startRow, count) {
3554
+ const frozen = history.totalRows;
3555
+ const result = [];
3556
+ for (let r = startRow; r < startRow + count; r++) if (r < 0 || r >= this.totalRows) result.push("");
3557
+ else if (r < frozen) result.push(...history.getPlainTextRows(r, 1));
3558
+ else {
3559
+ let liveRow = r - frozen;
3560
+ let found = false;
3561
+ for (const block of getLiveItems()) {
3562
+ if (liveRow < block.plainTextRows.length) {
3563
+ result.push(block.plainTextRows[liveRow]);
3564
+ found = true;
3565
+ break;
3566
+ }
3567
+ liveRow -= block.plainTextRows.length;
3568
+ }
3569
+ if (!found) result.push("");
3570
+ }
3571
+ return result;
3572
+ },
3573
+ getSource(row) {
3574
+ const frozen = history.totalRows;
3575
+ if (row < 0 || row >= this.totalRows) return null;
3576
+ if (row < frozen) {
3577
+ const hit = history.getItemAtRow(row);
3578
+ if (!hit) return null;
3579
+ return {
3580
+ type: "frozen",
3581
+ itemKey: hit.item.key,
3582
+ localRow: hit.localRow
3583
+ };
3584
+ }
3585
+ const liveItems = getLiveItems();
3586
+ let liveRow = row - frozen;
3587
+ for (const block of liveItems) {
3588
+ if (liveRow < block.rows.length) return {
3589
+ type: "live",
3590
+ itemIndex: block.itemIndex,
3591
+ localRow: liveRow
3592
+ };
3593
+ liveRow -= block.rows.length;
3594
+ }
3595
+ return null;
3596
+ },
3597
+ search(query) {
3598
+ if (!query) return [];
3599
+ const lowerQuery = query.toLowerCase();
3600
+ const matches = [];
3601
+ const frozen = history.totalRows;
3602
+ const frozenRowMatches = history.search(query);
3603
+ for (const row of frozenRowMatches) {
3604
+ const line = history.getPlainTextRows(row, 1)[0].toLowerCase();
3605
+ let pos = line.indexOf(lowerQuery);
3606
+ while (pos !== -1) {
3607
+ matches.push({
3608
+ row,
3609
+ startCol: pos,
3610
+ endCol: pos + query.length
3611
+ });
3612
+ pos = line.indexOf(lowerQuery, pos + 1);
3613
+ }
3614
+ }
3615
+ let rowOffset = 0;
3616
+ for (const block of getLiveItems()) {
3617
+ for (let i = 0; i < block.plainTextRows.length; i++) {
3618
+ const line = block.plainTextRows[i].toLowerCase();
3619
+ let pos = line.indexOf(lowerQuery);
3620
+ while (pos !== -1) {
3621
+ matches.push({
3622
+ row: frozen + rowOffset + i,
3623
+ startCol: pos,
3624
+ endCol: pos + query.length
3625
+ });
3626
+ pos = line.indexOf(lowerQuery, pos + 1);
3627
+ }
3628
+ }
3629
+ rowOffset += block.plainTextRows.length;
3630
+ }
3631
+ return matches;
3632
+ }
3633
+ };
3634
+ }
3635
+ //#endregion
3636
+ //#region packages/ag-term/src/text-surface.ts
3637
+ function createTextSurface(config) {
3638
+ const listeners = /* @__PURE__ */ new Set();
3639
+ function notify() {
3640
+ for (const fn of listeners) fn();
3641
+ }
3642
+ return {
3643
+ get id() {
3644
+ return config.id;
3645
+ },
3646
+ get document() {
3647
+ return config.document;
3648
+ },
3649
+ getText(startRow, startCol, endRow, endCol) {
3650
+ const rows = config.document.getRows(startRow, endRow - startRow + 1);
3651
+ const lines = [];
3652
+ for (let i = 0; i < rows.length; i++) {
3653
+ const plain = stripAnsi(rows[i]);
3654
+ const row = startRow + i;
3655
+ if (row === startRow && row === endRow) lines.push(plain.slice(startCol, endCol));
3656
+ else if (row === startRow) lines.push(plain.slice(startCol));
3657
+ else if (row === endRow) lines.push(plain.slice(0, endCol));
3658
+ else lines.push(plain);
3659
+ }
3660
+ return lines.join("\n");
3661
+ },
3662
+ search(query) {
3663
+ return config.document.search(query);
3664
+ },
3665
+ hitTest(viewportRow, viewportCol) {
3666
+ const docRow = config.viewportToDocument(viewportRow);
3667
+ if (docRow < 0 || docRow >= config.document.totalRows) return null;
3668
+ return {
3669
+ row: docRow,
3670
+ col: viewportCol
3671
+ };
3672
+ },
3673
+ reveal(row) {
3674
+ config.onReveal(row);
3675
+ notify();
3676
+ },
3677
+ notifyContentChange() {
3678
+ notify();
3679
+ },
3680
+ subscribe(listener) {
3681
+ listeners.add(listener);
3682
+ return () => {
3683
+ listeners.delete(listener);
3684
+ };
3685
+ },
3686
+ get capabilities() {
3687
+ return config.capabilities;
3688
+ }
3689
+ };
3690
+ }
3691
+ //#endregion
3692
+ //#region packages/ag-term/src/search-overlay.ts
3693
+ /**
3694
+ * Find all case-insensitive occurrences of `query` inside `text` and return
3695
+ * their character offsets. Empty query or empty text returns `[]`.
3696
+ *
3697
+ * This is the canonical silvery search-match algorithm. The same logic runs
3698
+ * inside `ListView`'s registered Searchable (to find matches across items)
3699
+ * and is exposed to consumers that render multi-segment items — a LogRow
3700
+ * whose searchable text is a concatenation of field values, but whose visual
3701
+ * rendering splits those fields across separate Text nodes, needs per-segment
3702
+ * ranges to highlight without re-implementing the semantics.
3703
+ *
3704
+ * Ranges are returned in ascending `start` order; overlapping matches are
3705
+ * not produced (`indexOf(..., start = last.start + 1)` advances past each
3706
+ * match start, not past its full length, preserving overlapping runs of
3707
+ * short queries — e.g. `"aa"` in `"aaaa"` yields `[0..2], [1..3], [2..4]`).
3708
+ *
3709
+ * Offsets are CHARACTER offsets into the input string. They are not column
3710
+ * offsets — multi-column wide glyphs are counted as one character here, the
3711
+ * same way the SearchProvider's match-row computation does.
3712
+ */
3713
+ function computeMatchRanges(text, query) {
3714
+ if (query === "" || text === "") return [];
3715
+ const lowerText = text.toLowerCase();
3716
+ const lowerQuery = query.toLowerCase();
3717
+ const ranges = [];
3718
+ let cursor = 0;
3719
+ while (cursor < lowerText.length) {
3720
+ const found = lowerText.indexOf(lowerQuery, cursor);
3721
+ if (found === -1) break;
3722
+ ranges.push({
3723
+ start: found,
3724
+ end: found + query.length
3725
+ });
3726
+ cursor = found + 1;
3727
+ }
3728
+ return ranges;
3729
+ }
3730
+ function createSearchState() {
3731
+ return {
3732
+ active: false,
3733
+ query: "",
3734
+ matches: [],
3735
+ currentMatch: -1,
3736
+ cursorPosition: 0
3737
+ };
3738
+ }
3739
+ /**
3740
+ * Update search state. searchFn is called when query changes to find matches.
3741
+ */
3742
+ function searchUpdate(action, state, searchFn) {
3743
+ switch (action.type) {
3744
+ case "open": return [{
3745
+ ...state,
3746
+ active: true,
3747
+ query: "",
3748
+ matches: [],
3749
+ currentMatch: -1,
3750
+ cursorPosition: 0
3751
+ }, [{ type: "render" }]];
3752
+ case "close": return [{
3753
+ ...state,
3754
+ active: false,
3755
+ query: "",
3756
+ cursorPosition: 0
3757
+ }, [{ type: "render" }]];
3758
+ case "input": {
3759
+ const query = state.query.slice(0, state.cursorPosition) + action.char + state.query.slice(state.cursorPosition);
3760
+ const cursorPosition = state.cursorPosition + 1;
3761
+ const matches = searchFn ? searchFn(query) : [];
3762
+ const currentMatch = matches.length > 0 ? 0 : -1;
3763
+ const effects = [{ type: "render" }];
3764
+ if (currentMatch >= 0) effects.push({
3765
+ type: "scrollTo",
3766
+ row: matches[0].row
3767
+ });
3768
+ return [{
3769
+ ...state,
3770
+ query,
3771
+ cursorPosition,
3772
+ matches,
3773
+ currentMatch
3774
+ }, effects];
3775
+ }
3776
+ case "backspace": {
3777
+ if (state.cursorPosition === 0) return [state, []];
3778
+ const query = state.query.slice(0, state.cursorPosition - 1) + state.query.slice(state.cursorPosition);
3779
+ const cursorPosition = state.cursorPosition - 1;
3780
+ const matches = searchFn ? searchFn(query) : [];
3781
+ const currentMatch = matches.length > 0 ? 0 : -1;
3782
+ const effects = [{ type: "render" }];
3783
+ if (currentMatch >= 0) effects.push({
3784
+ type: "scrollTo",
3785
+ row: matches[0].row
3786
+ });
3787
+ return [{
3788
+ ...state,
3789
+ query,
3790
+ cursorPosition,
3791
+ matches,
3792
+ currentMatch
3793
+ }, effects];
3794
+ }
3795
+ case "nextMatch": {
3796
+ if (state.matches.length === 0) return [state, []];
3797
+ const currentMatch = (state.currentMatch + 1) % state.matches.length;
3798
+ return [{
3799
+ ...state,
3800
+ currentMatch
3801
+ }, [{
3802
+ type: "scrollTo",
3803
+ row: state.matches[currentMatch].row
3804
+ }, { type: "render" }]];
3805
+ }
3806
+ case "prevMatch": {
3807
+ if (state.matches.length === 0) return [state, []];
3808
+ const currentMatch = (state.currentMatch - 1 + state.matches.length) % state.matches.length;
3809
+ return [{
3810
+ ...state,
3811
+ currentMatch
3812
+ }, [{
3813
+ type: "scrollTo",
3814
+ row: state.matches[currentMatch].row
3815
+ }, { type: "render" }]];
3816
+ }
3817
+ case "cursorLeft":
3818
+ if (state.cursorPosition === 0) return [state, []];
3819
+ return [{
3820
+ ...state,
3821
+ cursorPosition: state.cursorPosition - 1
3822
+ }, []];
3823
+ case "cursorRight":
3824
+ if (state.cursorPosition >= state.query.length) return [state, []];
3825
+ return [{
3826
+ ...state,
3827
+ cursorPosition: state.cursorPosition + 1
3828
+ }, []];
3829
+ case "cursorToStart":
3830
+ if (state.cursorPosition === 0) return [state, []];
3831
+ return [{
3832
+ ...state,
3833
+ cursorPosition: 0
3834
+ }, []];
3835
+ case "cursorToEnd":
3836
+ if (state.cursorPosition >= state.query.length) return [state, []];
3837
+ return [{
3838
+ ...state,
3839
+ cursorPosition: state.query.length
3840
+ }, []];
3841
+ case "deleteWordBack": {
3842
+ if (state.cursorPosition === 0) return [state, []];
3843
+ let pos = state.cursorPosition;
3844
+ while (pos > 0 && /\s/.test(state.query[pos - 1] ?? "")) pos--;
3845
+ while (pos > 0 && !/\s/.test(state.query[pos - 1] ?? "")) pos--;
3846
+ const query = state.query.slice(0, pos) + state.query.slice(state.cursorPosition);
3847
+ const cursorPosition = pos;
3848
+ const matches = searchFn ? searchFn(query) : [];
3849
+ const currentMatch = matches.length > 0 ? 0 : -1;
3850
+ const effects = [{ type: "render" }];
3851
+ if (currentMatch >= 0) effects.push({
3852
+ type: "scrollTo",
3853
+ row: matches[0].row
3854
+ });
3855
+ return [{
3856
+ ...state,
3857
+ query,
3858
+ cursorPosition,
3859
+ matches,
3860
+ currentMatch
3861
+ }, effects];
3862
+ }
3863
+ case "deleteToStart": {
3864
+ if (state.cursorPosition === 0) return [state, []];
3865
+ const query = state.query.slice(state.cursorPosition);
3866
+ const cursorPosition = 0;
3867
+ const matches = searchFn ? searchFn(query) : [];
3868
+ const currentMatch = matches.length > 0 ? 0 : -1;
3869
+ const effects = [{ type: "render" }];
3870
+ if (currentMatch >= 0) effects.push({
3871
+ type: "scrollTo",
3872
+ row: matches[0].row
3873
+ });
3874
+ return [{
3875
+ ...state,
3876
+ query,
3877
+ cursorPosition,
3878
+ matches,
3879
+ currentMatch
3880
+ }, effects];
3881
+ }
3882
+ }
3883
+ }
3884
+ /**
3885
+ * Render the search bar as a plain (no ANSI) string padded to `cols` width.
3886
+ * Format: " / query [2/15]" or " / query [no matches]" — padded with spaces.
3887
+ *
3888
+ * Caller decides how to display it — wrap with `\x1b[7m...\x1b[27m` for the
3889
+ * legacy overlay path, or stamp into a buffer's cells with the inverse
3890
+ * attribute for the compose+apply path
3891
+ * (see `applySearchBarToPaintBuffer` in `runtime/renderer.ts`).
3892
+ */
3893
+ function renderSearchBarPlain(state, cols) {
3894
+ const prefix = " / ";
3895
+ const matchInfo = state.matches.length > 0 ? ` [${state.currentMatch + 1}/${state.matches.length}]` : state.query ? " [no matches]" : "";
3896
+ return (prefix + state.query + matchInfo).padEnd(cols);
3897
+ }
3898
+ //#endregion
3899
+ export { DecMode as $, withMeasurer as A, createTerminalSelectionState as At, resolveNonTTYMode as B, resetScrollRegion as C, CursorProvider as Ct, supportsScrollRegions as D, createDragEvent as Dt, setScrollRegion as E, useCursor as Et, inspectTree as F, createInputEvents as G, Z_INDEX as H, isInspectorEnabled as I, resolveFromTerm as J, isTerm as K, countLines as L, disableInspector as M, terminalSelectionUpdate as Mt, enableInspector as N, createFocusManager as Nt, calcEdgeBasedScrollOffset as O, createDragState as Ot, inspectFrame as P, queryTextAreaSize as Q, createOutputTransformer as R, moveCursor as S, createCanvasAdapter as St, scrollUp as T, resetCursorState as Tt, generateHitRegionId as U, HitRegistry as V, resetHitRegionIdCounter as W, queryCellSize as X, resolveTermDef as Y, queryTextAreaPixels as Z, getTabOrder as _, findActiveCursorNode as _t, createTextSurface as a, queryTerminalVersion as at, splitPane as b, injectDOMStyles as bt, createHistoryBuffer as c, detectWidthConfigWithProbe as ct, applySelectionToBuffer as d, TERMTEST_SECTIONS as dt, queryMode as et, composeSearchHighlightCells as f, runTermtest as ft, getPaneIds as g, OSC133 as gt, findAdjacentPane as h, detectKittyWithProbe as ht, searchUpdate as i, querySecondaryDA as it, autoEnableInspector as j, extractText as jt, createPipeline as k, findDropTarget as kt, createHistoryItem as l, queryCursorFromStdio as lt, createLeaf as m, detectKittySupport as mt, createSearchState as n, queryDeviceAttributes as nt, createListDocument as o, queryTertiaryDA as ot, composeSelectionCells as p, detectKittyFromStdio as pt, isTermDef as q, renderSearchBarPlain as r, queryPrimaryDA as rt, composeViewport as s, applyWidthConfig as st, computeMatchRanges as t, queryModes as tt, createVirtualScrollback as u, queryCursorPosition as ut, removePane as v, DOMRenderBuffer as vt, scrollDown as w, createCursorStore as wt, swapPanes as x, CanvasRenderBuffer as xt, resizeSplit as y, createDOMAdapter as yt, isTTY as z };
3900
+
3901
+ //# sourceMappingURL=src-DbqcSrhy.mjs.map