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