@promptctl/rich-js 0.18.0 → 0.19.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 (244) hide show
  1. package/README.md +9 -7
  2. package/dist/core/anchor.d.ts +45 -0
  3. package/dist/core/anchor.js +39 -0
  4. package/dist/core/ansi.d.ts +65 -0
  5. package/dist/core/ansi.js +231 -0
  6. package/dist/core/box.d.ts +145 -0
  7. package/dist/core/box.js +372 -0
  8. package/dist/core/cells.d.ts +192 -0
  9. package/dist/core/cells.js +334 -0
  10. package/dist/core/clock.d.ts +48 -0
  11. package/dist/core/clock.js +42 -0
  12. package/dist/core/color.d.ts +326 -0
  13. package/dist/core/color.js +1225 -0
  14. package/dist/core/console.d.ts +196 -0
  15. package/dist/core/console.js +596 -0
  16. package/dist/core/easing.d.ts +86 -0
  17. package/dist/core/easing.js +171 -0
  18. package/dist/core/emoji.d.ts +24 -0
  19. package/dist/core/emoji.js +391 -0
  20. package/dist/core/env.d.ts +16 -0
  21. package/dist/core/env.js +16 -0
  22. package/dist/core/export-html.d.ts +59 -0
  23. package/dist/core/export-html.js +197 -0
  24. package/dist/core/export-lines.d.ts +130 -0
  25. package/dist/core/export-lines.js +159 -0
  26. package/dist/core/export-svg.d.ts +49 -0
  27. package/dist/core/export-svg.js +213 -0
  28. package/dist/core/fnv1a.d.ts +10 -0
  29. package/dist/core/fnv1a.js +17 -0
  30. package/dist/core/highlighter.d.ts +33 -0
  31. package/dist/core/highlighter.js +190 -0
  32. package/dist/core/json.d.ts +27 -0
  33. package/dist/core/json.js +53 -0
  34. package/dist/core/markup.d.ts +140 -0
  35. package/dist/core/markup.js +654 -0
  36. package/dist/core/measure.d.ts +27 -0
  37. package/dist/core/measure.js +67 -0
  38. package/dist/core/memo.d.ts +19 -0
  39. package/dist/core/memo.js +34 -0
  40. package/dist/core/oklch.d.ts +161 -0
  41. package/dist/core/oklch.js +312 -0
  42. package/dist/core/osc8.d.ts +68 -0
  43. package/dist/core/osc8.js +78 -0
  44. package/dist/core/paint.d.ts +107 -0
  45. package/dist/core/paint.js +150 -0
  46. package/dist/core/place.d.ts +30 -0
  47. package/dist/core/place.js +49 -0
  48. package/dist/core/pretty.d.ts +247 -0
  49. package/dist/core/pretty.js +906 -0
  50. package/dist/core/protocol.d.ts +305 -0
  51. package/dist/core/protocol.js +183 -0
  52. package/dist/core/render.d.ts +87 -0
  53. package/dist/core/render.js +105 -0
  54. package/dist/core/segment.d.ts +162 -0
  55. package/dist/core/segment.js +488 -0
  56. package/dist/core/spinnerData.d.ts +12 -0
  57. package/dist/core/spinnerData.js +117 -0
  58. package/dist/core/strip.d.ts +152 -0
  59. package/dist/core/strip.js +326 -0
  60. package/dist/core/style.d.ts +189 -0
  61. package/dist/core/style.js +758 -0
  62. package/dist/core/subscription.d.ts +12 -0
  63. package/dist/core/subscription.js +12 -0
  64. package/dist/core/text.d.ts +283 -0
  65. package/dist/core/text.js +1097 -0
  66. package/dist/core/wrap.d.ts +46 -0
  67. package/dist/core/wrap.js +140 -0
  68. package/dist/host/app.d.ts +133 -0
  69. package/dist/host/app.js +285 -0
  70. package/dist/host/host-environment.d.ts +26 -0
  71. package/dist/host/host-environment.js +44 -0
  72. package/dist/host/index.d.ts +6 -0
  73. package/dist/host/index.js +3 -0
  74. package/dist/host/terminal-host.d.ts +180 -0
  75. package/dist/host/terminal-host.js +186 -0
  76. package/dist/index.d.ts +98 -0
  77. package/dist/index.js +114 -0
  78. package/dist/node/prompt.d.ts +34 -0
  79. package/dist/node/prompt.js +51 -0
  80. package/dist/node/save.d.ts +18 -0
  81. package/dist/node/save.js +24 -0
  82. package/dist/node/terminal-host.d.ts +114 -0
  83. package/dist/node/terminal-host.js +227 -0
  84. package/dist/node/traceback.d.ts +38 -0
  85. package/dist/node/traceback.js +98 -0
  86. package/dist/renderables/align.d.ts +16 -0
  87. package/dist/renderables/align.js +26 -0
  88. package/dist/renderables/columns.d.ts +77 -0
  89. package/dist/renderables/columns.js +191 -0
  90. package/dist/renderables/constrain.d.ts +15 -0
  91. package/dist/renderables/constrain.js +31 -0
  92. package/dist/renderables/effect.d.ts +98 -0
  93. package/dist/renderables/effect.js +160 -0
  94. package/dist/renderables/embed.d.ts +107 -0
  95. package/dist/renderables/embed.js +160 -0
  96. package/dist/renderables/flexStrip.d.ts +41 -0
  97. package/dist/renderables/flexStrip.js +200 -0
  98. package/dist/renderables/group.d.ts +14 -0
  99. package/dist/renderables/group.js +22 -0
  100. package/dist/renderables/layout.d.ts +75 -0
  101. package/dist/renderables/layout.js +223 -0
  102. package/dist/renderables/live.d.ts +74 -0
  103. package/dist/renderables/live.js +188 -0
  104. package/dist/renderables/markdown.d.ts +38 -0
  105. package/dist/renderables/markdown.js +587 -0
  106. package/dist/renderables/padding.d.ts +59 -0
  107. package/dist/renderables/padding.js +147 -0
  108. package/dist/renderables/panel.d.ts +114 -0
  109. package/dist/renderables/panel.js +303 -0
  110. package/dist/renderables/progress.d.ts +178 -0
  111. package/dist/renderables/progress.js +362 -0
  112. package/dist/renderables/progressBar.d.ts +31 -0
  113. package/dist/renderables/progressBar.js +64 -0
  114. package/dist/renderables/prompt.d.ts +71 -0
  115. package/dist/renderables/prompt.js +165 -0
  116. package/dist/renderables/ratio.d.ts +123 -0
  117. package/dist/renderables/ratio.js +213 -0
  118. package/dist/renderables/rule.d.ts +26 -0
  119. package/dist/renderables/rule.js +86 -0
  120. package/dist/renderables/spinner.d.ts +57 -0
  121. package/dist/renderables/spinner.js +103 -0
  122. package/dist/renderables/status.d.ts +29 -0
  123. package/dist/renderables/status.js +46 -0
  124. package/dist/renderables/syntax.d.ts +65 -0
  125. package/dist/renderables/syntax.js +166 -0
  126. package/dist/renderables/table.d.ts +232 -0
  127. package/dist/renderables/table.js +973 -0
  128. package/dist/renderables/traceback.d.ts +32 -0
  129. package/dist/renderables/traceback.js +279 -0
  130. package/dist/renderables/tree.d.ts +76 -0
  131. package/dist/renderables/tree.js +177 -0
  132. package/dist/renderables/viewport.d.ts +114 -0
  133. package/dist/renderables/viewport.js +196 -0
  134. package/dist/template-bindings/color-funcs.d.ts +109 -0
  135. package/dist/template-bindings/color-funcs.js +243 -0
  136. package/dist/template-bindings/helpers.d.ts +26 -0
  137. package/dist/template-bindings/helpers.js +33 -0
  138. package/dist/template-bindings/index.d.ts +119 -0
  139. package/dist/template-bindings/index.js +162 -0
  140. package/dist/template-bindings/palette-funcs.d.ts +96 -0
  141. package/dist/template-bindings/palette-funcs.js +145 -0
  142. package/dist/template-bindings/style-funcs.d.ts +28 -0
  143. package/dist/template-bindings/style-funcs.js +157 -0
  144. package/dist/themes/buildPalette.d.ts +57 -0
  145. package/dist/themes/buildPalette.js +100 -0
  146. package/dist/themes/colorMath.d.ts +94 -0
  147. package/dist/themes/colorMath.js +280 -0
  148. package/dist/themes/colorRef.d.ts +36 -0
  149. package/dist/themes/colorRef.js +78 -0
  150. package/dist/themes/data/ansi-tables.d.ts +14 -0
  151. package/dist/themes/data/ansi-tables.js +49 -0
  152. package/dist/themes/data/atom-one-dark.d.ts +3 -0
  153. package/dist/themes/data/atom-one-dark.js +177 -0
  154. package/dist/themes/data/atom-one-light.d.ts +3 -0
  155. package/dist/themes/data/atom-one-light.js +177 -0
  156. package/dist/themes/data/catppuccin-frappe.d.ts +3 -0
  157. package/dist/themes/data/catppuccin-frappe.js +177 -0
  158. package/dist/themes/data/catppuccin-latte.d.ts +3 -0
  159. package/dist/themes/data/catppuccin-latte.js +177 -0
  160. package/dist/themes/data/catppuccin-macchiato.d.ts +3 -0
  161. package/dist/themes/data/catppuccin-macchiato.js +177 -0
  162. package/dist/themes/data/catppuccin-mocha.d.ts +3 -0
  163. package/dist/themes/data/catppuccin-mocha.js +177 -0
  164. package/dist/themes/data/cyberpunk.d.ts +17 -0
  165. package/dist/themes/data/cyberpunk.js +193 -0
  166. package/dist/themes/data/default.d.ts +3 -0
  167. package/dist/themes/data/default.js +160 -0
  168. package/dist/themes/data/dracula.d.ts +3 -0
  169. package/dist/themes/data/dracula.js +177 -0
  170. package/dist/themes/data/flexoki.d.ts +3 -0
  171. package/dist/themes/data/flexoki.js +177 -0
  172. package/dist/themes/data/gruvbox.d.ts +3 -0
  173. package/dist/themes/data/gruvbox.js +177 -0
  174. package/dist/themes/data/index.d.ts +35 -0
  175. package/dist/themes/data/index.js +64 -0
  176. package/dist/themes/data/monokai.d.ts +3 -0
  177. package/dist/themes/data/monokai.js +177 -0
  178. package/dist/themes/data/nord.d.ts +3 -0
  179. package/dist/themes/data/nord.js +177 -0
  180. package/dist/themes/data/rose-pine-dawn.d.ts +3 -0
  181. package/dist/themes/data/rose-pine-dawn.js +177 -0
  182. package/dist/themes/data/rose-pine-moon.d.ts +3 -0
  183. package/dist/themes/data/rose-pine-moon.js +177 -0
  184. package/dist/themes/data/rose-pine.d.ts +3 -0
  185. package/dist/themes/data/rose-pine.js +177 -0
  186. package/dist/themes/data/solarized-dark.d.ts +3 -0
  187. package/dist/themes/data/solarized-dark.js +177 -0
  188. package/dist/themes/data/solarized-light.d.ts +3 -0
  189. package/dist/themes/data/solarized-light.js +177 -0
  190. package/dist/themes/data/svg-export.d.ts +3 -0
  191. package/dist/themes/data/svg-export.js +177 -0
  192. package/dist/themes/data/textual-ansi.d.ts +3 -0
  193. package/dist/themes/data/textual-ansi.js +160 -0
  194. package/dist/themes/data/textual-dark.d.ts +3 -0
  195. package/dist/themes/data/textual-dark.js +160 -0
  196. package/dist/themes/data/textual-light.d.ts +3 -0
  197. package/dist/themes/data/textual-light.js +160 -0
  198. package/dist/themes/data/tokyo-night.d.ts +3 -0
  199. package/dist/themes/data/tokyo-night.js +177 -0
  200. package/dist/themes/data/types.d.ts +31 -0
  201. package/dist/themes/data/types.js +10 -0
  202. package/dist/themes/palette.d.ts +45 -0
  203. package/dist/themes/palette.js +71 -0
  204. package/dist/themes/ramp.d.ts +70 -0
  205. package/dist/themes/ramp.js +103 -0
  206. package/dist/themes/registry.d.ts +44 -0
  207. package/dist/themes/registry.js +105 -0
  208. package/dist/themes/terminalThemes.d.ts +49 -0
  209. package/dist/themes/terminalThemes.js +63 -0
  210. package/dist/themes/transpose.d.ts +87 -0
  211. package/dist/themes/transpose.js +138 -0
  212. package/dist/widgets/button.d.ts +45 -0
  213. package/dist/widgets/button.js +177 -0
  214. package/dist/widgets/checkbox.d.ts +38 -0
  215. package/dist/widgets/checkbox.js +135 -0
  216. package/dist/widgets/dropdown.d.ts +64 -0
  217. package/dist/widgets/dropdown.js +416 -0
  218. package/dist/widgets/event-router.d.ts +71 -0
  219. package/dist/widgets/event-router.js +629 -0
  220. package/dist/widgets/focus-manager.d.ts +45 -0
  221. package/dist/widgets/focus-manager.js +191 -0
  222. package/dist/widgets/hit.d.ts +64 -0
  223. package/dist/widgets/hit.js +113 -0
  224. package/dist/widgets/index.d.ts +20 -0
  225. package/dist/widgets/index.js +14 -0
  226. package/dist/widgets/ink.d.ts +22 -0
  227. package/dist/widgets/ink.js +31 -0
  228. package/dist/widgets/slider.d.ts +54 -0
  229. package/dist/widgets/slider.js +247 -0
  230. package/dist/widgets/static-item.d.ts +34 -0
  231. package/dist/widgets/static-item.js +54 -0
  232. package/dist/widgets/text-input.d.ts +283 -0
  233. package/dist/widgets/text-input.js +1174 -0
  234. package/dist/widgets/themed-widget.d.ts +20 -0
  235. package/dist/widgets/themed-widget.js +89 -0
  236. package/dist/widgets/toggle.d.ts +41 -0
  237. package/dist/widgets/toggle.js +157 -0
  238. package/dist/widgets/types.d.ts +87 -0
  239. package/dist/widgets/types.js +30 -0
  240. package/dist/widgets/widget-app.d.ts +65 -0
  241. package/dist/widgets/widget-app.js +206 -0
  242. package/dist/widgets/widget-base.d.ts +42 -0
  243. package/dist/widgets/widget-base.js +172 -0
  244. package/package.json +16 -2
@@ -0,0 +1,758 @@
1
+ /**
2
+ * Immutable style descriptors — colors, text attributes, links, metadata.
3
+ */
4
+ import { COLOR_NAMES, ColorSpec, SURFACE_BLACK, } from "./color.js";
5
+ import { shiftAnchor } from "./anchor.js";
6
+ import { Memo } from "./memo.js";
7
+ // [LAW:one-way-deps] `core/style` depends only on `core/color`. The substrate
8
+ // a translucent colour is flattened over is `SURFACE_BLACK`, defined beside
9
+ // `compositeOver`.
10
+ // --- Attribute definitions ---
11
+ /**
12
+ * Canonical text-attribute inventory. Single source of truth consumed by
13
+ * `Style.parse` / `Style.toString` and the template bindings — adding an
14
+ * attribute here propagates to every styling surface without a second
15
+ * list to keep in sync. [LAW:one-source-of-truth]
16
+ */
17
+ export const ATTRIBUTE_NAMES = [
18
+ "bold",
19
+ "dim",
20
+ "italic",
21
+ "underline",
22
+ "blink",
23
+ "blink2",
24
+ "reverse",
25
+ "conceal",
26
+ "strike",
27
+ "underline2",
28
+ "frame",
29
+ "encircle",
30
+ "overline",
31
+ ];
32
+ // The SGR parameter that turns each attribute on. `ansi.ts` reads it back.
33
+ export const ATTRIBUTE_SGR = {
34
+ bold: 1,
35
+ dim: 2,
36
+ italic: 3,
37
+ underline: 4,
38
+ blink: 5,
39
+ blink2: 6,
40
+ reverse: 7,
41
+ conceal: 8,
42
+ strike: 9,
43
+ underline2: 21,
44
+ frame: 51,
45
+ encircle: 52,
46
+ overline: 53,
47
+ };
48
+ /**
49
+ * Canonical short-attribute aliases (`b` → `bold`, `i` → `italic`, …).
50
+ * Single source of truth consumed by `Style.parse` and the template
51
+ * bindings — adding an alias here makes it available everywhere.
52
+ * [LAW:one-source-of-truth]
53
+ */
54
+ export const ATTRIBUTE_SHORT_ALIASES = {
55
+ b: "bold",
56
+ d: "dim",
57
+ i: "italic",
58
+ u: "underline",
59
+ s: "strike",
60
+ r: "reverse",
61
+ o: "overline",
62
+ uu: "underline2",
63
+ };
64
+ export class StyleSyntaxError extends Error {
65
+ constructor(message, options) {
66
+ super(message, options);
67
+ this.name = "StyleSyntaxError";
68
+ }
69
+ }
70
+ // [LAW:one-source-of-truth] The single source for parsed Style instances.
71
+ const styleParseMemo = new Memo();
72
+ // --- Style ---
73
+ export class Style {
74
+ color;
75
+ bgcolor;
76
+ bold;
77
+ dim;
78
+ italic;
79
+ underline;
80
+ blink;
81
+ blink2;
82
+ reverse;
83
+ conceal;
84
+ strike;
85
+ underline2;
86
+ frame;
87
+ encircle;
88
+ overline;
89
+ link;
90
+ meta;
91
+ /**
92
+ * Where this style's cells sat in the output of whatever drew them; see
93
+ * `./anchor.ts`. It draws nothing, and it makes two styles that draw alike
94
+ * unequal, because they came from different places.
95
+ */
96
+ anchor;
97
+ constructor(options = {}) {
98
+ this.color = resolveColor(options.color);
99
+ this.bgcolor = resolveBackground(options.bgcolor);
100
+ this.bold = options.bold;
101
+ this.dim = options.dim;
102
+ this.italic = options.italic;
103
+ this.underline = options.underline;
104
+ this.blink = options.blink;
105
+ this.blink2 = options.blink2;
106
+ this.reverse = options.reverse;
107
+ this.conceal = options.conceal;
108
+ this.strike = options.strike;
109
+ this.underline2 = options.underline2;
110
+ this.frame = options.frame;
111
+ this.encircle = options.encircle;
112
+ this.overline = options.overline;
113
+ this.link = options.link;
114
+ this.meta = options.meta;
115
+ this.anchor = options.anchor;
116
+ }
117
+ get isNull() {
118
+ return (this.color === undefined &&
119
+ this.bgcolor === undefined &&
120
+ this.bold === undefined &&
121
+ this.dim === undefined &&
122
+ this.italic === undefined &&
123
+ this.underline === undefined &&
124
+ this.blink === undefined &&
125
+ this.blink2 === undefined &&
126
+ this.reverse === undefined &&
127
+ this.conceal === undefined &&
128
+ this.strike === undefined &&
129
+ this.underline2 === undefined &&
130
+ this.frame === undefined &&
131
+ this.encircle === undefined &&
132
+ this.overline === undefined &&
133
+ this.link === undefined &&
134
+ this.meta === undefined &&
135
+ this.anchor === undefined);
136
+ }
137
+ get transparentBackground() {
138
+ return this.bgcolor === undefined || this.bgcolor.isDefault;
139
+ }
140
+ get backgroundStyle() {
141
+ return new Style({ bgcolor: this.bgcolor });
142
+ }
143
+ /**
144
+ * This style with `overrides` in place of its own fields.
145
+ *
146
+ * [LAW:one-source-of-truth] The field list is the class's own fields, so a
147
+ * field added to `Style` is carried by every copy without being named here.
148
+ */
149
+ with(overrides) {
150
+ return new Style({ ...this, ...overrides });
151
+ }
152
+ get withoutColor() {
153
+ return this.with({ color: undefined, bgcolor: undefined });
154
+ }
155
+ // [LAW:types-are-the-program] Structural clone helper — pure rearrangement
156
+ // of fields, no policy. Sanitization of the URL is the trust boundary's
157
+ // job (see RichText in text.ts); Style stays a faithful container so
158
+ // callers that have already sanitized are not silently re-mutated.
159
+ withLink(link) {
160
+ return this.with({ link });
161
+ }
162
+ clearMetaAndLinks() {
163
+ return this.with({ link: undefined, meta: undefined });
164
+ }
165
+ /**
166
+ * The same look, drawn from the place `anchor` names, or from nowhere.
167
+ */
168
+ withAnchor(anchor) {
169
+ return this.with({ anchor });
170
+ }
171
+ /**
172
+ * This style on the cell `cells` to the right: its anchor shifted along its
173
+ * row, or the style itself when it has no anchor.
174
+ */
175
+ shiftedBy(cells) {
176
+ return this.anchor ? this.withAnchor(shiftAnchor(this.anchor, cells)) : this;
177
+ }
178
+ /**
179
+ * Merges two styles. `other`'s values take priority.
180
+ */
181
+ add(other) {
182
+ if (!other || other.isNull)
183
+ return this;
184
+ if (this.isNull)
185
+ return other;
186
+ return new Style({
187
+ color: other.color ?? this.color,
188
+ bgcolor: other.bgcolor ?? this.bgcolor,
189
+ bold: other.bold ?? this.bold,
190
+ dim: other.dim ?? this.dim,
191
+ italic: other.italic ?? this.italic,
192
+ underline: other.underline ?? this.underline,
193
+ blink: other.blink ?? this.blink,
194
+ blink2: other.blink2 ?? this.blink2,
195
+ reverse: other.reverse ?? this.reverse,
196
+ conceal: other.conceal ?? this.conceal,
197
+ strike: other.strike ?? this.strike,
198
+ underline2: other.underline2 ?? this.underline2,
199
+ frame: other.frame ?? this.frame,
200
+ encircle: other.encircle ?? this.encircle,
201
+ overline: other.overline ?? this.overline,
202
+ link: other.link ?? this.link,
203
+ meta: other.meta && this.meta
204
+ ? { ...this.meta, ...other.meta }
205
+ : other.meta ?? this.meta,
206
+ anchor: other.anchor ?? this.anchor,
207
+ });
208
+ }
209
+ equals(other) {
210
+ if (this === other)
211
+ return true;
212
+ return (this.color?.name === other.color?.name &&
213
+ this.bgcolor?.name === other.bgcolor?.name &&
214
+ this.bold === other.bold &&
215
+ this.dim === other.dim &&
216
+ this.italic === other.italic &&
217
+ this.underline === other.underline &&
218
+ this.blink === other.blink &&
219
+ this.blink2 === other.blink2 &&
220
+ this.reverse === other.reverse &&
221
+ this.conceal === other.conceal &&
222
+ this.strike === other.strike &&
223
+ this.underline2 === other.underline2 &&
224
+ this.frame === other.frame &&
225
+ this.encircle === other.encircle &&
226
+ this.overline === other.overline &&
227
+ this.link === other.link &&
228
+ anchorsEqual(this.anchor, other.anchor));
229
+ }
230
+ /**
231
+ * The colours this style puts on screen at `colorSystem`: each flattened
232
+ * onto what lies beneath it — the background onto the terminal's black, the
233
+ * foreground onto that background — then downgraded to the depth. The one
234
+ * account of what a style draws: `toSgrCodes` encodes exactly these, and a
235
+ * renderable choosing between two ways of drawing measures them.
236
+ */
237
+ drawnColors(colorSystem) {
238
+ // [LAW:dataflow-not-control-flow] Always resolve a substrate and flatten
239
+ // alpha before downgrade. Opaque colors short-circuit inside compositeOver,
240
+ // so the same code path runs every render — the alpha value is the data,
241
+ // not a branch.
242
+ const surface = SURFACE_BLACK;
243
+ const bgFlat = this.bgcolor?.flattenAlpha(surface);
244
+ const fgSubstrate = bgFlat?.getTruecolor(undefined, false) ?? surface;
245
+ const fgFlat = this.color?.flattenAlpha(fgSubstrate);
246
+ const at = (c) => colorSystem !== undefined ? c.downgrade(colorSystem) : c;
247
+ return {
248
+ color: fgFlat === undefined ? undefined : at(fgFlat),
249
+ bgcolor: bgFlat === undefined ? undefined : at(bgFlat),
250
+ };
251
+ }
252
+ /**
253
+ * Returns the SGR parameter list this style emits (e.g. `"1;31;48;2;0;0;255"`),
254
+ * or `""` when the style has no SGR contribution. Excludes OSC 8 link bytes —
255
+ * links are not SGR. The segment encoder (`segmentToString`) wraps it in
256
+ * `\x1b[...m`; styled text reaches the wire only through that encoder.
257
+ *
258
+ * [LAW:one-source-of-truth] One computation of SGR codes.
259
+ */
260
+ toSgrCodes(colorSystem) {
261
+ if (this.isNull)
262
+ return "";
263
+ // Attributes, then foreground, then background: Python Rich 9d8f9a3's
264
+ // `_make_ansi_codes` order, so a byte pinned against the reference holds.
265
+ // An attribute set false writes nothing, as there. It overrides an
266
+ // inherited attribute when styles combine, and every styled run is written
267
+ // after a reset, so no off code is ever needed.
268
+ const drawn = this.drawnColors(colorSystem);
269
+ return [
270
+ ...ATTRIBUTE_NAMES.filter((name) => this[name] === true).map((name) => `${ATTRIBUTE_SGR[name]}`),
271
+ ...(drawn.color?.getAnsiCodes(true) ?? []),
272
+ ...(drawn.bgcolor?.getAnsiCodes(false) ?? []),
273
+ ].join(";");
274
+ }
275
+ /**
276
+ * Serializes to a canonical string that round-trips through `Style.parse()`.
277
+ */
278
+ toString() {
279
+ if (this.isNull)
280
+ return "none";
281
+ const parts = [];
282
+ // Negated attributes first
283
+ for (const name of ATTRIBUTE_NAMES) {
284
+ if (this[name] === false)
285
+ parts.push(`not ${name}`);
286
+ }
287
+ // Positive attributes
288
+ for (const name of ATTRIBUTE_NAMES) {
289
+ if (this[name] === true)
290
+ parts.push(name);
291
+ }
292
+ if (this.color)
293
+ parts.push(this.color.name);
294
+ if (this.bgcolor)
295
+ parts.push(`on ${this.bgcolor.name}`);
296
+ if (this.link)
297
+ parts.push(`link ${this.link}`);
298
+ return parts.join(" ") || "none";
299
+ }
300
+ // --- Static methods ---
301
+ static null() {
302
+ return NULL_STYLE;
303
+ }
304
+ static fromColor(color, bgcolor) {
305
+ return new Style({ color, bgcolor });
306
+ }
307
+ static fromMeta(meta) {
308
+ return new Style({ meta });
309
+ }
310
+ static pickFirst(...args) {
311
+ for (const a of args) {
312
+ if (a !== undefined)
313
+ return a;
314
+ }
315
+ throw new Error("All arguments are undefined");
316
+ }
317
+ static combine(styles) {
318
+ let result = NULL_STYLE;
319
+ for (const s of styles) {
320
+ if (s)
321
+ result = result.add(s);
322
+ }
323
+ return result;
324
+ }
325
+ static chain(...styles) {
326
+ return Style.combine(styles);
327
+ }
328
+ static normalize(definition) {
329
+ return definition.trim().replace(/\s+/g, " ");
330
+ }
331
+ /**
332
+ * Parse a space-separated style definition. Memoized (`Memo`).
333
+ *
334
+ * A definition only: `"bold red"` parses, `"repr.number"` does not. Names
335
+ * belong to a `Theme`, and `Theme.resolve` is where one is looked up.
336
+ */
337
+ static parse(definition) {
338
+ const normalized = Style.normalize(definition);
339
+ if (normalized === "" || normalized === "none")
340
+ return NULL_STYLE;
341
+ return styleParseMemo.get(normalized, parseStyleDefinition);
342
+ }
343
+ }
344
+ export const NULL_STYLE = new Style();
345
+ // --- StyleStack ---
346
+ export class StyleStack {
347
+ stack;
348
+ constructor(base = NULL_STYLE) {
349
+ this.stack = [base];
350
+ }
351
+ get current() {
352
+ return this.stack[this.stack.length - 1];
353
+ }
354
+ push(style) {
355
+ this.stack.push(this.current.add(style));
356
+ }
357
+ pop() {
358
+ if (this.stack.length <= 1) {
359
+ throw new Error("Cannot pop the base style from StyleStack");
360
+ }
361
+ return this.stack.pop();
362
+ }
363
+ }
364
+ // --- Theme ---
365
+ const STYLE_NAME_RE = /^[a-z][a-z0-9._-]*$/;
366
+ /**
367
+ * `over` added to `base`, the outer style winning as `Style.add` has it. A
368
+ * `Style` base takes the addition now, and so does `""`, the definition that
369
+ * adds nothing; any other string keeps it as a `LayeredStyle` for the render
370
+ * that resolves the string.
371
+ */
372
+ export function layerStyle(base, over) {
373
+ if (base instanceof Style)
374
+ return base.add(over);
375
+ if (base === "")
376
+ return over;
377
+ if (typeof base === "string")
378
+ return { under: base, over };
379
+ return { under: base.under, over: base.over.add(over) };
380
+ }
381
+ export class Theme {
382
+ styles;
383
+ constructor(definitions = {}, options) {
384
+ this.styles = new Map();
385
+ const inherit = options?.inherit !== false;
386
+ if (inherit) {
387
+ for (const [name, style] of Object.entries(DEFAULT_STYLES)) {
388
+ this.styles.set(name, style);
389
+ }
390
+ }
391
+ for (const [name, def] of Object.entries(definitions)) {
392
+ if (!STYLE_NAME_RE.test(name)) {
393
+ throw new Error(`Invalid style name: "${name}" (must be lowercase, start with letter, contain only letters, digits, ".", "-", "_")`);
394
+ }
395
+ this.styles.set(name, Style.parse(def));
396
+ }
397
+ }
398
+ get(name) {
399
+ return this.styles.get(name);
400
+ }
401
+ /**
402
+ * The style a `TextStyle` stands for under this theme: a name this theme
403
+ * defines, otherwise a definition for `Style.parse`, which throws
404
+ * `StyleSyntaxError` when the string is neither. A `LayeredStyle` is its
405
+ * `under` resolved here with its `over` added on top.
406
+ *
407
+ * [LAW:one-source-of-truth] The theme is the one map from names to styles.
408
+ * The lookup stays outside `Style.parse`'s process-wide cache on purpose:
409
+ * cached there, the first console to resolve `repr.number` would decide it
410
+ * for every console after it, whatever theme each was given.
411
+ */
412
+ resolve(style) {
413
+ if (style instanceof Style)
414
+ return style;
415
+ if (typeof style !== "string")
416
+ return this.resolve(style.under).add(style.over);
417
+ return this.styles.get(style) ?? Style.parse(style);
418
+ }
419
+ has(name) {
420
+ return this.styles.has(name);
421
+ }
422
+ }
423
+ // --- Parse helpers ---
424
+ const ATTRIBUTE_SET = new Set(ATTRIBUTE_NAMES);
425
+ // The words each position of a definition can hold: a bare token is a keyword,
426
+ // an attribute or a colour name; `on` takes a colour name; `not` an attribute.
427
+ // The short aliases are left out, since `b` is no help to someone who typed
428
+ // `bolt` and a one-letter name sits one edit from every other short word.
429
+ // [LAW:one-source-of-truth] Derived from the lists the parser accepts from; the
430
+ // keywords are the three `parseStyleDefinition` reads before anything else.
431
+ const FOREGROUND_WORDS = ["not", "on", "link", ...ATTRIBUTE_NAMES, ...COLOR_NAMES];
432
+ /**
433
+ * Optimal string alignment distance: insertions, deletions, substitutions, and
434
+ * swaps of two neighbouring letters each cost one edit, so `cyna` is as near
435
+ * `cyan` as `rd` is to `red`. Kept to three rows, since only the two before the
436
+ * current one are ever read.
437
+ */
438
+ function editDistance(a, b) {
439
+ let before = [];
440
+ let previous = Array.from({ length: b.length + 1 }, (_, j) => j);
441
+ for (let i = 1; i <= a.length; i++) {
442
+ const current = [i];
443
+ for (let j = 1; j <= b.length; j++) {
444
+ const swap = i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1];
445
+ current[j] = Math.min(previous[j] + 1, current[j - 1] + 1, previous[j - 1] + Number(a[i - 1] !== b[j - 1]), swap ? before[j - 2] + 1 : Infinity);
446
+ }
447
+ [before, previous] = [previous, current];
448
+ }
449
+ return previous[b.length];
450
+ }
451
+ /**
452
+ * The ` (did you mean …?)` clause for a word no position accepted, or `""`
453
+ * when nothing in `words` is a plausible typo of it.
454
+ *
455
+ * Plausible means within one edit per three letters, at least one, which lets
456
+ * `rd` reach `red` and keeps `hello` from reaching `yellow`. Every name tied
457
+ * for nearest is offered: `gold` is one edit from `bold` and from three
458
+ * `goldN` colours, and choosing among them would be a guess the reader cannot
459
+ * tell from a finding. Case is not an edit, because the colour parser ignores
460
+ * it: `Cyna` is as near `cyan` as `cyna` is.
461
+ *
462
+ * This runs on every render that meets the bad style, since only successful
463
+ * parses are cached. Two words differ by at least their difference in length,
464
+ * so a name outside the budget on length alone is never measured — which is
465
+ * also what keeps a long garbage token from costing length × length per name.
466
+ */
467
+ function nearMiss(word, words) {
468
+ const typed = word.toLowerCase();
469
+ const budget = Math.max(1, Math.floor(typed.length / 3));
470
+ const scored = words
471
+ .filter((name) => Math.abs(name.length - typed.length) <= budget)
472
+ .map((name) => ({ name, distance: editDistance(typed, name) }));
473
+ const nearest = Math.min(budget, ...scored.map((s) => s.distance));
474
+ const names = scored
475
+ .filter((s) => s.distance === nearest)
476
+ .map((s) => `"${s.name}"`)
477
+ .sort();
478
+ return names.length === 0 ? "" : ` (did you mean ${names.join(" or ")}?)`;
479
+ }
480
+ /**
481
+ * A style token read as a colour. The colour parser's own error is the reason
482
+ * the token failed, so it rides along twice: in the message, for a reader with
483
+ * only the text, and as `cause`, for code that wants the original error.
484
+ *
485
+ * [LAW:single-enforcer] Both colour positions in a definition rewrap here, and
486
+ * the near miss is named here rather than by the colour parser: only a
487
+ * definition knows whether the token could also have been an attribute.
488
+ */
489
+ function parseStyleColor(token, failure, words) {
490
+ try {
491
+ return ColorSpec.parse(token);
492
+ }
493
+ catch (cause) {
494
+ throw new StyleSyntaxError(`${failure} "${token}"${nearMiss(token, words)}: ${String(cause)}`, {
495
+ cause,
496
+ });
497
+ }
498
+ }
499
+ function parseStyleDefinition(definition) {
500
+ const tokens = definition.split(/\s+/);
501
+ const opts = {};
502
+ let i = 0;
503
+ while (i < tokens.length) {
504
+ const token = tokens[i];
505
+ // "not <attr>"
506
+ if (token === "not") {
507
+ i++;
508
+ const next = tokens[i];
509
+ if (!next) {
510
+ throw new StyleSyntaxError(`Expected attribute after "not" in style definition`);
511
+ }
512
+ const attrName = ATTRIBUTE_SHORT_ALIASES[next] ?? next;
513
+ if (!ATTRIBUTE_SET.has(attrName)) {
514
+ throw new StyleSyntaxError(`Invalid attribute: "${next}"${nearMiss(next, ATTRIBUTE_NAMES)}`);
515
+ }
516
+ opts[attrName] = false;
517
+ i++;
518
+ continue;
519
+ }
520
+ // "on <color>"
521
+ if (token === "on") {
522
+ i++;
523
+ const next = tokens[i];
524
+ if (!next) {
525
+ throw new StyleSyntaxError(`Expected color after "on" in style definition`);
526
+ }
527
+ opts.bgcolor = parseStyleColor(next, "Invalid background color", COLOR_NAMES);
528
+ i++;
529
+ continue;
530
+ }
531
+ // "link <url>"
532
+ if (token === "link") {
533
+ i++;
534
+ const url = tokens[i];
535
+ if (url) {
536
+ opts.link = url;
537
+ }
538
+ i++;
539
+ continue;
540
+ }
541
+ // Attribute (full name)
542
+ const resolvedAttr = ATTRIBUTE_SHORT_ALIASES[token] ?? token;
543
+ if (ATTRIBUTE_SET.has(resolvedAttr)) {
544
+ opts[resolvedAttr] = true;
545
+ i++;
546
+ continue;
547
+ }
548
+ // Must be a color
549
+ opts.color = parseStyleColor(token, "Invalid style definition", FOREGROUND_WORDS);
550
+ i++;
551
+ }
552
+ return new Style(opts);
553
+ }
554
+ function resolveColor(c) {
555
+ if (c === undefined)
556
+ return undefined;
557
+ if (c instanceof ColorSpec)
558
+ return c;
559
+ return ColorSpec.parse(c);
560
+ }
561
+ // A fully transparent background draws nothing over whatever lies beneath it,
562
+ // so it is no background: a style added on top of another keeps that one's,
563
+ // which is what Textual's `#00000000` means. A transparent foreground is not
564
+ // the same, it draws its glyph in whatever lies beneath it — the background,
565
+ // or `SURFACE_BLACK` when there is none — so it stays a colour.
566
+ function resolveBackground(c) {
567
+ const spec = resolveColor(c);
568
+ return spec?.value?.alpha === 0 ? undefined : spec;
569
+ }
570
+ // --- DEFAULT_STYLES ---
571
+ // [LAW:one-source-of-truth] Single canonical mapping of semantic names to styles
572
+ export const DEFAULT_STYLES = {
573
+ none: NULL_STYLE,
574
+ reset: new Style({
575
+ color: ColorSpec.default(),
576
+ bgcolor: ColorSpec.default(),
577
+ bold: false,
578
+ dim: false,
579
+ italic: false,
580
+ underline: false,
581
+ blink: false,
582
+ blink2: false,
583
+ reverse: false,
584
+ conceal: false,
585
+ strike: false,
586
+ underline2: false,
587
+ frame: false,
588
+ encircle: false,
589
+ overline: false,
590
+ }),
591
+ bold: new Style({ bold: true }),
592
+ dim: new Style({ dim: true }),
593
+ italic: new Style({ italic: true }),
594
+ underline: new Style({ underline: true }),
595
+ blink: new Style({ blink: true }),
596
+ blink2: new Style({ blink2: true }),
597
+ reverse: new Style({ reverse: true }),
598
+ conceal: new Style({ conceal: true }),
599
+ strike: new Style({ strike: true }),
600
+ underline2: new Style({ underline2: true }),
601
+ frame: new Style({ frame: true }),
602
+ encircle: new Style({ encircle: true }),
603
+ overline: new Style({ overline: true }),
604
+ // Table
605
+ "table.header": new Style({ bold: true }),
606
+ "table.footer": new Style({ bold: true }),
607
+ "table.cell": NULL_STYLE,
608
+ "table.title": new Style({ italic: true }),
609
+ "table.caption": new Style({ italic: true, dim: true }),
610
+ // Repr/highlighting — Rich 15.0.0's `rich.default_styles`, in its order
611
+ "repr.ellipsis": Style.parse("yellow"),
612
+ "repr.indent": Style.parse("dim green"),
613
+ "repr.error": Style.parse("bold red"),
614
+ "repr.str": Style.parse("not bold not italic green"),
615
+ "repr.brace": Style.parse("bold"),
616
+ "repr.comma": Style.parse("bold"),
617
+ "repr.ipv4": Style.parse("bold bright_green"),
618
+ "repr.ipv6": Style.parse("bold bright_green"),
619
+ "repr.eui48": Style.parse("bold bright_green"),
620
+ "repr.eui64": Style.parse("bold bright_green"),
621
+ "repr.tag_start": Style.parse("bold"),
622
+ "repr.tag_name": Style.parse("bold bright_magenta"),
623
+ "repr.tag_contents": Style.parse("default"),
624
+ "repr.tag_end": Style.parse("bold"),
625
+ "repr.attrib_name": Style.parse("not italic yellow"),
626
+ "repr.attrib_equal": Style.parse("bold"),
627
+ "repr.attrib_value": Style.parse("not italic magenta"),
628
+ "repr.number": Style.parse("bold not italic cyan"),
629
+ "repr.number_complex": Style.parse("bold not italic cyan"),
630
+ "repr.bool_true": Style.parse("italic bright_green"),
631
+ "repr.bool_false": Style.parse("italic bright_red"),
632
+ "repr.none": Style.parse("italic magenta"),
633
+ "repr.url": Style.parse("not bold not italic underline bright_blue"),
634
+ "repr.uuid": Style.parse("not bold bright_yellow"),
635
+ "repr.call": Style.parse("bold magenta"),
636
+ "repr.path": Style.parse("magenta"),
637
+ "repr.filename": Style.parse("bright_magenta"),
638
+ // Log
639
+ "log.time": Style.parse("cyan dim"),
640
+ "log.message": NULL_STYLE,
641
+ "log.path": Style.parse("dim"),
642
+ "log.line_no": Style.parse("cyan"),
643
+ "log.level": NULL_STYLE,
644
+ // Rule
645
+ "rule.line": Style.parse("green"),
646
+ "rule.text": Style.parse("bold"),
647
+ // JSON
648
+ "json.brace": Style.parse("bold"),
649
+ "json.bool_true": Style.parse("italic bright_green"),
650
+ "json.bool_false": Style.parse("italic bright_red"),
651
+ "json.key": Style.parse("bold blue"),
652
+ "json.null": Style.parse("italic magenta"),
653
+ "json.number": Style.parse("bold not italic cyan"),
654
+ "json.str": Style.parse("not bold not italic green"),
655
+ // Syntax
656
+ "syntax.keyword": Style.parse("bold magenta"),
657
+ "syntax.constant": Style.parse("italic bright_magenta"),
658
+ "syntax.string": Style.parse("green"),
659
+ "syntax.number": Style.parse("cyan"),
660
+ "syntax.comment": Style.parse("dim italic"),
661
+ "syntax.line_number": Style.parse("dim"),
662
+ // Ink and ground both from the fixed 256-colour greys: the terminal theme's
663
+ // own foreground is dark on a light theme and would vanish on grey27. At 16
664
+ // colours `Syntax` draws it without the pair (`groundedStyle`).
665
+ "syntax.line_number.highlight": Style.parse("bold grey93 on grey27"),
666
+ // Markdown
667
+ "markdown.h1": Style.parse("bold underline"),
668
+ "markdown.h2": Style.parse("bold"),
669
+ "markdown.h3": Style.parse("bold dim"),
670
+ "markdown.h4": Style.parse("bold dim italic"),
671
+ // A fixed cyan, not the theme's: `cyan` is dark enough on some themes to
672
+ // sink into grey11. Bold, as Rich's is: at 16 colours a renderable draws it
673
+ // without its pair (`groundedStyle`), and bold is what still marks code.
674
+ "markdown.code": Style.parse("bold dark_turquoise on grey11"),
675
+ "markdown.hr": Style.parse("yellow"),
676
+ "markdown.link": Style.parse("bright_blue"),
677
+ "markdown.link_url": Style.parse("blue"),
678
+ // Progress
679
+ "progress.description": NULL_STYLE,
680
+ "progress.percentage": Style.parse("cyan"),
681
+ "progress.remaining": Style.parse("cyan"),
682
+ "progress.elapsed": Style.parse("cyan"),
683
+ "progress.spinner": Style.parse("green"),
684
+ "progress.download": Style.parse("green"),
685
+ "progress.filesize": Style.parse("green"),
686
+ "progress.filesize.total": Style.parse("green"),
687
+ "progress.data.speed": Style.parse("red"),
688
+ // Bar
689
+ "bar.back": Style.parse("grey23"),
690
+ "bar.complete": Style.parse("magenta"),
691
+ "bar.finished": Style.parse("green"),
692
+ "bar.pulse": Style.parse("magenta"),
693
+ // Scrollbar
694
+ "scrollbar.thumb": NULL_STYLE,
695
+ "scrollbar.track": Style.parse("grey37"),
696
+ // Tree
697
+ "tree": NULL_STYLE,
698
+ "tree.guide": NULL_STYLE,
699
+ // Status
700
+ "status.spinner": Style.parse("green"),
701
+ "status.message": NULL_STYLE,
702
+ // Prompt
703
+ "prompt": NULL_STYLE,
704
+ "prompt.choices": Style.parse("bold magenta"),
705
+ "prompt.default": Style.parse("bold cyan"),
706
+ "prompt.invalid": Style.parse("red"),
707
+ "prompt.invalid.choice": Style.parse("red"),
708
+ // Inspect
709
+ "inspect.attr": Style.parse("yellow italic"),
710
+ "inspect.attr.dunder": Style.parse("yellow italic dim"),
711
+ "inspect.callable": Style.parse("bold magenta"),
712
+ "inspect.error": Style.parse("bold red"),
713
+ "inspect.help": Style.parse("cyan"),
714
+ "inspect.doc": Style.parse("dim"),
715
+ "inspect.value.border": Style.parse("green"),
716
+ // Traceback
717
+ "traceback.border": Style.parse("red"),
718
+ "traceback.text": Style.parse("red"),
719
+ "traceback.title": Style.parse("bold red"),
720
+ "traceback.exc_type": Style.parse("bold bright_red"),
721
+ "traceback.exc_value": NULL_STYLE,
722
+ "traceback.offset": Style.parse("bold bright_red"),
723
+ // Scope
724
+ "scope.border": Style.parse("blue"),
725
+ "scope.key": Style.parse("italic"),
726
+ "scope.key.special": Style.parse("dim italic"),
727
+ // Pretty
728
+ "pretty": NULL_STYLE,
729
+ // ISO8601
730
+ "iso8601.date": Style.parse("blue"),
731
+ "iso8601.time": Style.parse("magenta"),
732
+ "iso8601.timezone": Style.parse("yellow"),
733
+ // The parts ISO8601Highlighter lays inside a date or time. Rich's theme names
734
+ // none of them, so they draw nothing; they are here so a theme can set them
735
+ // and so none reaches `onStyleError` as an unknown name.
736
+ "iso8601.year": NULL_STYLE,
737
+ "iso8601.month": NULL_STYLE,
738
+ "iso8601.day": NULL_STYLE,
739
+ "iso8601.week": NULL_STYLE,
740
+ "iso8601.hour": NULL_STYLE,
741
+ "iso8601.minute": NULL_STYLE,
742
+ "iso8601.second": NULL_STYLE,
743
+ "iso8601.frac": NULL_STYLE,
744
+ "iso8601.ms": NULL_STYLE,
745
+ "iso8601.hyphen": NULL_STYLE,
746
+ };
747
+ /**
748
+ * The theme a render resolves names against when nothing supplied one — what
749
+ * a renderable sees outside a `Console`, as in `renderToString`.
750
+ */
751
+ export const DEFAULT_THEME = new Theme();
752
+ function anchorsEqual(a, b) {
753
+ if (a === b)
754
+ return true;
755
+ if (!a || !b)
756
+ return false;
757
+ return a.owner === b.owner && a.row === b.row && a.col === b.col && anchorsEqual(a.inner, b.inner);
758
+ }