@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
package/README.md CHANGED
@@ -342,7 +342,7 @@ console.print(new Greeting());
342
342
 
343
343
  ```typescript
344
344
  const console = new Console({
345
- colorSystem: "truecolor", // null | "auto" | "standard" | "256" | "truecolor" | "windows"
345
+ colorSystem: "truecolor", // null | "auto" | "none" | "ansi" | "256" | "truecolor"
346
346
  width: 120, // override terminal width
347
347
  stderr: true, // write to stderr
348
348
  record: true, // record output for export
@@ -354,14 +354,15 @@ const console = new Console({
354
354
  When `record: true`, export output after the fact:
355
355
 
356
356
  ```typescript
357
- import { saveHtml } from "@promptctl/rich-js/node/save";
357
+ import { SOLARIZED_LIGHT, SVG_EXPORT_THEME } from "@promptctl/rich-js";
358
+ import { saveSvg } from "@promptctl/rich-js/node/save";
358
359
 
359
- const text = console.exportText();
360
- const html = console.exportHtml();
361
- saveHtml(console, "output.html");
360
+ const text = console.exportText({ clear: false });
361
+ const html = console.exportHtml({ theme: SOLARIZED_LIGHT, clear: false });
362
+ saveSvg(console, "output.svg", { theme: SVG_EXPORT_THEME });
362
363
  ```
363
364
 
364
- `saveText` / `saveHtml` live on the `node/save` subpath because they import `node:fs`; the main barrel stays browser-safe.
365
+ `saveText`, `saveHtml` and `saveSvg` live on the `node/save` subpath because they import `node:fs`; the main barrel stays browser-safe. The [console guide](https://brandon-fryslie.github.io/rich-js/master/console#exporting) covers what each export draws, the themes it takes, and when it clears the recording.
365
366
 
366
367
  ## Demos
367
368
 
@@ -384,8 +385,9 @@ npm run demo
384
385
  | `npm run demo-inputs` | rich-config | The widgets — checkbox, toggle, slider, dropdown, text input, button — driving a theme and palette viewer. |
385
386
  | `npm run demo:dropdown` | dropdown-demo | Three `Dropdown` widgets: a plain one, one filtered as you type, and one whose options change every few seconds. |
386
387
  | `npm run template-bindings` | rich-template-bindings | Type a template on the left and watch it render on the right. |
387
- | `npm run themes-and-color-studio` | themes-and-color-studio | A printed tour of colours, palettes, bundled themes, and contrast. Prints once and exits; set `EXPORT_HTML=out.html` to save it as HTML too. |
388
+ | `npm run themes-and-color-studio` | themes-and-color-studio | A printed tour of colours, palettes, bundled themes, and contrast. Prints once and exits; set any of `EXPORT_HTML=out.html`, `EXPORT_SVG=out.svg` and `EXPORT_TEXT=out.txt` to also save it to each file named. |
388
389
  | `npm run strip` | rich-strip | Every built-in `Joiner`, printed side by side. Prints once and exits. |
390
+ | `npm run effects-feel` | effects-feel | Shimmer, pulse, drift, sparkle, fade-in and dissolve-out on a powerline strip and a run of text, at a frame rate, colour depth and ground you choose. A demo for agreeing how the effects feel; `npm run effects-feel -- --help` lists the flags. |
389
391
  | `npm run markup-plugins` | rich-markup-plugins | Custom markup tags registered through `MarkupRegistry`. Prints once and exits. |
390
392
  | `npm run viewport` | rich-viewport | A cursor walking a list taller than its window, kept in view by `Viewport.ensureVisible`. |
391
393
 
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Anchor — where a cell sat in the output of the thing that drew it.
3
+ *
4
+ * A widget nested inside a `Layout` cell inside a `Panel` does not know where
5
+ * it lands on screen, and nothing can tell it before it renders: `Align`
6
+ * centres each line only after rendering it, so the lines of one widget can
7
+ * start at different columns, and a `Viewport` shows whichever of its
8
+ * content's rows its offset selects. An origin handed down through the render
9
+ * options would be a number each container has to compute before it knows
10
+ * the answer, and one more thing every container must forward correctly.
11
+ *
12
+ * So the position travels up instead, on the cells themselves. The owner
13
+ * stamps each segment of its output with its row and column there
14
+ * (`Segment.anchorLines`), the stamp rides on the segment's `Style`, which
15
+ * every container already carries through untouched, and whoever holds the
16
+ * composed frame reads any cell's owner and position straight off it
17
+ * (`Segment.anchorAt`). Containers do nothing, so no container can get it
18
+ * wrong.
19
+ *
20
+ * [LAW:single-enforcer] Two things can falsify a stamp. Cutting a segment in
21
+ * two starts the right half at a later column than the stamp names:
22
+ * `Segment.splitCells` is where a segment is cut, and it shifts the right
23
+ * half's anchor (`shiftAnchor`); a crop that keeps the left half keeps a stamp
24
+ * that is already true. Laying the text out again moves every cell: a
25
+ * `RichText` does that, so it admits no anchor (`admitStyle` in `./text.ts`),
26
+ * and the owner that renders it stamps what it draws.
27
+ */
28
+ /**
29
+ * The position of a segment's first cell in its owner's output, and the
30
+ * anchor that segment carried before this owner stamped it — the owner nested
31
+ * inside this one, if any. Innermost last: follow `inner` to reach the owner
32
+ * that drew the cell itself.
33
+ */
34
+ export interface Anchor {
35
+ /** The thing that drew this cell, compared by identity. */
36
+ readonly owner: object;
37
+ readonly row: number;
38
+ readonly col: number;
39
+ readonly inner: Anchor | undefined;
40
+ }
41
+ /**
42
+ * The anchor of the cell `cells` to the right of the one `anchor` names, at
43
+ * every level of the nesting — each level names the same cell.
44
+ */
45
+ export declare function shiftAnchor(anchor: Anchor, cells: number): Anchor;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Anchor — where a cell sat in the output of the thing that drew it.
3
+ *
4
+ * A widget nested inside a `Layout` cell inside a `Panel` does not know where
5
+ * it lands on screen, and nothing can tell it before it renders: `Align`
6
+ * centres each line only after rendering it, so the lines of one widget can
7
+ * start at different columns, and a `Viewport` shows whichever of its
8
+ * content's rows its offset selects. An origin handed down through the render
9
+ * options would be a number each container has to compute before it knows
10
+ * the answer, and one more thing every container must forward correctly.
11
+ *
12
+ * So the position travels up instead, on the cells themselves. The owner
13
+ * stamps each segment of its output with its row and column there
14
+ * (`Segment.anchorLines`), the stamp rides on the segment's `Style`, which
15
+ * every container already carries through untouched, and whoever holds the
16
+ * composed frame reads any cell's owner and position straight off it
17
+ * (`Segment.anchorAt`). Containers do nothing, so no container can get it
18
+ * wrong.
19
+ *
20
+ * [LAW:single-enforcer] Two things can falsify a stamp. Cutting a segment in
21
+ * two starts the right half at a later column than the stamp names:
22
+ * `Segment.splitCells` is where a segment is cut, and it shifts the right
23
+ * half's anchor (`shiftAnchor`); a crop that keeps the left half keeps a stamp
24
+ * that is already true. Laying the text out again moves every cell: a
25
+ * `RichText` does that, so it admits no anchor (`admitStyle` in `./text.ts`),
26
+ * and the owner that renders it stamps what it draws.
27
+ */
28
+ /**
29
+ * The anchor of the cell `cells` to the right of the one `anchor` names, at
30
+ * every level of the nesting — each level names the same cell.
31
+ */
32
+ export function shiftAnchor(anchor, cells) {
33
+ return {
34
+ owner: anchor.owner,
35
+ row: anchor.row,
36
+ col: anchor.col + cells,
37
+ inner: anchor.inner && shiftAnchor(anchor.inner, cells),
38
+ };
39
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * ANSI bytes back into styled text — a port of Rich's `AnsiDecoder`
3
+ * (rich/ansi.py) and `Text.from_ansi`.
4
+ *
5
+ * The rendering pipeline runs one way, `Style` → SGR bytes; this runs it
6
+ * backwards, for output some program already wrote: a capture of an example's
7
+ * own `new Console()`, a CLI's output shown inside a Panel, a log replayed into
8
+ * a Table cell.
9
+ *
10
+ * [LAW:types-are-the-program] Every colour decodes to the `ColorSpec` its
11
+ * escape names — `31` the standard colour 1, `38;5;n` palette index n,
12
+ * `38;2;r;g;b` that truecolor — and none is resolved to RGB here. A standard
13
+ * colour is a slot in whatever theme draws it, so one captured run exports
14
+ * correctly under a light theme and a dark one; resolved here, dark mode would
15
+ * show light mode's colours. Resolution belongs to `export-lines`.
16
+ *
17
+ * [LAW:types-are-the-program] The link is decoder state of its own, beside
18
+ * the SGR style, not a field of it: a terminal ends a hyperlink only at an OSC
19
+ * 8 close, and an SGR reset inside the link leaves it open. Rich keeps the
20
+ * link in the SGR style, so its reset drops it.
21
+ *
22
+ * Carriage returns are the other departure, and the same argument. Rich splits
23
+ * lines with Python's `splitlines`, which also breaks at `\r`, so its `a\rb`
24
+ * is two lines. Here a line ends only at `\n`, and `\r` returns to the first
25
+ * column, so later text overwrites earlier text character by character:
26
+ * `50%\r100%` is `100%`, `Downloading\rDone` is `Doneloading`, and `done\r`
27
+ * is still `done`. Columns are counted in characters, not cells, so a wide
28
+ * character overwritten by a narrow one is where this and a terminal differ.
29
+ * Erase in line (`\x1b[K`) is honoured for the same reason, because a redraw
30
+ * is usually `\r\x1b[K` and the old text must not show through a shorter new
31
+ * one.
32
+ *
33
+ * Everything else follows Rich: malformed SGR parameters are skipped rather
34
+ * than refused, and every other escape is dropped. It is not a terminal
35
+ * emulator — any other cursor movement is dropped, not performed.
36
+ *
37
+ * Tier 5 of `src/core/`: it builds `RichText`, so it sits above `text`, and
38
+ * `RichText` cannot offer a `fromAnsi` static without importing upward.
39
+ */
40
+ import { RichText } from "./text.js";
41
+ import type { RichTextOptions } from "./text.js";
42
+ /**
43
+ * A stateful decoder: the style an escape sets carries on across lines, and
44
+ * across calls, as it does on a terminal. Use one decoder per stream and hand
45
+ * it whole lines, so output read a line at a time decodes as it would have
46
+ * all at once. It buffers nothing: a chunk cut mid-line or mid-escape decodes
47
+ * as the line it appears to be.
48
+ */
49
+ export declare class AnsiDecoder {
50
+ private sgr;
51
+ private link;
52
+ /** One `RichText` per line of `ansi`; a final newline ends a line rather than starting one. */
53
+ decode(ansi: string): RichText[];
54
+ /**
55
+ * One line of output, holding no `\n`, written the way a terminal writes
56
+ * it: `\r` returns to the first column, and text overwrites from there.
57
+ */
58
+ decodeLine(line: string): RichText;
59
+ }
60
+ /**
61
+ * `ansi` as one `RichText`, its lines joined by `\n` — Rich's
62
+ * `Text.from_ansi`. `options` describe the result the way they describe any
63
+ * `RichText`.
64
+ */
65
+ export declare function decodeAnsi(ansi: string, options?: RichTextOptions): RichText;
@@ -0,0 +1,231 @@
1
+ /**
2
+ * ANSI bytes back into styled text — a port of Rich's `AnsiDecoder`
3
+ * (rich/ansi.py) and `Text.from_ansi`.
4
+ *
5
+ * The rendering pipeline runs one way, `Style` → SGR bytes; this runs it
6
+ * backwards, for output some program already wrote: a capture of an example's
7
+ * own `new Console()`, a CLI's output shown inside a Panel, a log replayed into
8
+ * a Table cell.
9
+ *
10
+ * [LAW:types-are-the-program] Every colour decodes to the `ColorSpec` its
11
+ * escape names — `31` the standard colour 1, `38;5;n` palette index n,
12
+ * `38;2;r;g;b` that truecolor — and none is resolved to RGB here. A standard
13
+ * colour is a slot in whatever theme draws it, so one captured run exports
14
+ * correctly under a light theme and a dark one; resolved here, dark mode would
15
+ * show light mode's colours. Resolution belongs to `export-lines`.
16
+ *
17
+ * [LAW:types-are-the-program] The link is decoder state of its own, beside
18
+ * the SGR style, not a field of it: a terminal ends a hyperlink only at an OSC
19
+ * 8 close, and an SGR reset inside the link leaves it open. Rich keeps the
20
+ * link in the SGR style, so its reset drops it.
21
+ *
22
+ * Carriage returns are the other departure, and the same argument. Rich splits
23
+ * lines with Python's `splitlines`, which also breaks at `\r`, so its `a\rb`
24
+ * is two lines. Here a line ends only at `\n`, and `\r` returns to the first
25
+ * column, so later text overwrites earlier text character by character:
26
+ * `50%\r100%` is `100%`, `Downloading\rDone` is `Doneloading`, and `done\r`
27
+ * is still `done`. Columns are counted in characters, not cells, so a wide
28
+ * character overwritten by a narrow one is where this and a terminal differ.
29
+ * Erase in line (`\x1b[K`) is honoured for the same reason, because a redraw
30
+ * is usually `\r\x1b[K` and the old text must not show through a shorter new
31
+ * one.
32
+ *
33
+ * Everything else follows Rich: malformed SGR parameters are skipped rather
34
+ * than refused, and every other escape is dropped. It is not a terminal
35
+ * emulator — any other cursor movement is dropped, not performed.
36
+ *
37
+ * Tier 5 of `src/core/`: it builds `RichText`, so it sits above `text`, and
38
+ * `RichText` cannot offer a `fromAnsi` static without importing upward.
39
+ */
40
+ import { ColorSpec } from "./color.js";
41
+ import { osc8Sequences } from "./osc8.js";
42
+ import { ATTRIBUTE_NAMES, ATTRIBUTE_SGR, NULL_STYLE, Style } from "./style.js";
43
+ import { RichText } from "./text.js";
44
+ /**
45
+ * Every escape but OSC 8, which `osc8Sequences` reads before this runs. In
46
+ * order: a carriage return; an SGR sequence (group 1, its parameters — a
47
+ * private marker such as the `>` of `\x1b[>4;2m` makes it some other CSI);
48
+ * erase in line (group 2, its mode); any other CSI sequence; any other string
49
+ * escape — OSC, DCS, APC, PM, SOS — run to the terminators OSC 8 accepts or
50
+ * cut off by the end of the line; any other escape, ECMA-48's intermediates
51
+ * and one final byte (`\x1b(B`, `\x1b)0`, `\x1b#8`, `\x1bc`). Only the first
52
+ * three become tokens.
53
+ */
54
+ const ESCAPE = /\r|\x1b\[([0-9;:]*)m|\x1b\[([012]?)K|\x1b\[[0-?]*[ -/]*[@-~]|\x1b[\]P_^X][\s\S]*?(?:\x07|\x1b\\|\x9c|$)|\x1b[ -/]*[0-~]/g;
55
+ function* escapeTokens(bytes) {
56
+ let at = 0;
57
+ for (const match of bytes.matchAll(ESCAPE)) {
58
+ if (match.index > at)
59
+ yield { kind: "text", text: bytes.slice(at, match.index) };
60
+ if (match[0] === "\r")
61
+ yield { kind: "return" };
62
+ if (match[1] !== undefined)
63
+ yield { kind: "sgr", params: match[1] };
64
+ if (match[2] !== undefined)
65
+ yield { kind: "erase", mode: (match[2] || "0") };
66
+ at = match.index + match[0].length;
67
+ }
68
+ if (at < bytes.length)
69
+ yield { kind: "text", text: bytes.slice(at) };
70
+ }
71
+ // [LAW:one-source-of-truth] OSC 8 is read by the reader `osc8.ts` owns, so
72
+ // the terminators a link may end with cannot differ between writing and
73
+ // reading it back.
74
+ function* tokens(line) {
75
+ let at = 0;
76
+ for (const sequence of osc8Sequences(line)) {
77
+ yield* escapeTokens(line.slice(at, sequence.index));
78
+ yield { kind: "link", uri: sequence.uri };
79
+ at = sequence.index + sequence.length;
80
+ }
81
+ yield* escapeTokens(line.slice(at));
82
+ }
83
+ function adding(options) {
84
+ const added = new Style(options);
85
+ return (style) => style.add(added);
86
+ }
87
+ function next(rest) {
88
+ const step = rest.next();
89
+ return step.done ? undefined : step.value;
90
+ }
91
+ /**
92
+ * `5;n` or `2;r;g;b`, the arguments of `38` and `48`. A sequence cut short
93
+ * names no colour, and the codes it did consume are spent, as in Rich.
94
+ */
95
+ function extendedColor(rest) {
96
+ const kind = next(rest);
97
+ if (kind === 5) {
98
+ const n = next(rest);
99
+ return n === undefined ? undefined : ColorSpec.fromAnsi(n);
100
+ }
101
+ if (kind === 2) {
102
+ const [r, g, b] = [next(rest), next(rest), next(rest)];
103
+ return b === undefined ? undefined : ColorSpec.fromRgb(r, g, b);
104
+ }
105
+ return undefined;
106
+ }
107
+ // [LAW:dataflow-not-control-flow] Every code is an operation on the style,
108
+ // reset included; a code this table does not name is the identity.
109
+ const SGR_OPS = new Map([
110
+ [0, () => NULL_STYLE],
111
+ // [LAW:one-source-of-truth] The codes that turn an attribute on are the
112
+ // ones `Style.toSgrCodes` writes; only the codes that turn one off are ours.
113
+ ...ATTRIBUTE_NAMES.map((name) => [ATTRIBUTE_SGR[name], adding({ [name]: true })]),
114
+ [22, adding({ bold: false, dim: false })],
115
+ [23, adding({ italic: false })],
116
+ // ECMA-48: 24 is "neither singly nor doubly underlined", 25 "steady" at
117
+ // either speed. Rich's table clears only the first of each pair.
118
+ [24, adding({ underline: false, underline2: false })],
119
+ [25, adding({ blink: false, blink2: false })],
120
+ [27, adding({ reverse: false })],
121
+ [28, adding({ conceal: false })],
122
+ [29, adding({ strike: false })],
123
+ [38, (style, rest) => style.add(new Style({ color: extendedColor(rest) }))],
124
+ [39, adding({ color: ColorSpec.default() })],
125
+ [48, (style, rest) => style.add(new Style({ bgcolor: extendedColor(rest) }))],
126
+ [49, adding({ bgcolor: ColorSpec.default() })],
127
+ // Underline colour has no field on Style; its arguments are still spent, or
128
+ // `58;2;1;2;3` would read on as bold, dim and italic.
129
+ [58, (style, rest) => (extendedColor(rest), style)],
130
+ [54, adding({ frame: false, encircle: false })],
131
+ [55, adding({ overline: false })],
132
+ ...Array.from({ length: 8 }, (_, n) => [
133
+ [30 + n, adding({ color: ColorSpec.fromAnsi(n) })],
134
+ [40 + n, adding({ bgcolor: ColorSpec.fromAnsi(n) })],
135
+ [90 + n, adding({ color: ColorSpec.fromAnsi(n + 8) })],
136
+ [100 + n, adding({ bgcolor: ColorSpec.fromAnsi(n + 8) })],
137
+ ]).flat(),
138
+ ]);
139
+ const identity = (style) => style;
140
+ /**
141
+ * `params` applied to `style`. An empty parameter is 0 and one above 255 is
142
+ * 255; one that is not a number — a colon sub-parameter — is skipped, as in
143
+ * Rich.
144
+ */
145
+ function applySgr(style, params) {
146
+ const codes = params
147
+ .split(";")
148
+ .filter((code) => /^\d*$/.test(code))
149
+ .map((code) => Math.min(255, Number(code)))
150
+ .values();
151
+ let result = style;
152
+ for (const code of codes)
153
+ result = (SGR_OPS.get(code) ?? identity)(result, codes);
154
+ return result;
155
+ }
156
+ const BLANK = [" ", NULL_STYLE];
157
+ /** Erase in line: the cells before the cursor go blank, the ones from it on go. */
158
+ function erase(cells, column, mode) {
159
+ if (mode !== "0")
160
+ cells.fill(BLANK, 0, Math.min(column + 1, cells.length));
161
+ if (mode !== "1")
162
+ cells.length = Math.min(column, cells.length);
163
+ }
164
+ /**
165
+ * A stateful decoder: the style an escape sets carries on across lines, and
166
+ * across calls, as it does on a terminal. Use one decoder per stream and hand
167
+ * it whole lines, so output read a line at a time decodes as it would have
168
+ * all at once. It buffers nothing: a chunk cut mid-line or mid-escape decodes
169
+ * as the line it appears to be.
170
+ */
171
+ export class AnsiDecoder {
172
+ sgr = NULL_STYLE;
173
+ link = undefined;
174
+ /** One `RichText` per line of `ansi`; a final newline ends a line rather than starting one. */
175
+ decode(ansi) {
176
+ return (ansi.match(/[^\n]*\n|[^\n]+/g) ?? []).map((line) => this.decodeLine(line.replace(/\r?\n$/, "")));
177
+ }
178
+ /**
179
+ * One line of output, holding no `\n`, written the way a terminal writes
180
+ * it: `\r` returns to the first column, and text overwrites from there.
181
+ */
182
+ decodeLine(line) {
183
+ const cells = [];
184
+ let column = 0;
185
+ for (const token of tokens(line)) {
186
+ switch (token.kind) {
187
+ case "text": {
188
+ const style = this.sgr.withLink(this.link);
189
+ for (const char of token.text)
190
+ cells[column++] = [char, style];
191
+ break;
192
+ }
193
+ case "return":
194
+ column = 0;
195
+ break;
196
+ case "erase":
197
+ erase(cells, column, token.mode);
198
+ break;
199
+ case "sgr":
200
+ this.sgr = applySgr(this.sgr, token.params);
201
+ break;
202
+ case "link":
203
+ this.link = token.uri === "" ? undefined : token.uri;
204
+ break;
205
+ }
206
+ }
207
+ const runs = [];
208
+ for (const [char, style] of cells) {
209
+ const last = runs.at(-1);
210
+ if (last?.[1].equals(style))
211
+ last[0] += char;
212
+ else
213
+ runs.push([char, style]);
214
+ }
215
+ return RichText.assemble(runs);
216
+ }
217
+ }
218
+ /**
219
+ * `ansi` as one `RichText`, its lines joined by `\n` — Rich's
220
+ * `Text.from_ansi`. `options` describe the result the way they describe any
221
+ * `RichText`.
222
+ */
223
+ export function decodeAnsi(ansi, options) {
224
+ const result = new RichText("", options);
225
+ new AnsiDecoder().decode(ansi).forEach((line, index) => {
226
+ if (index > 0)
227
+ result.append("\n");
228
+ result.append(line);
229
+ });
230
+ return result;
231
+ }
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Box-drawing character sets for borders and table grids.
3
+ *
4
+ * A style is declared as an 8x4 character grid — one line per row a table can
5
+ * draw, one column per position within that row:
6
+ *
7
+ * ┌─┬┐ top border
8
+ * │ ││ header content
9
+ * ├─┼┤ header separator
10
+ * │ ││ body content
11
+ * ├─┼┤ row separator
12
+ * ├─┼┤ footer separator
13
+ * │ ││ footer content
14
+ * └─┴┘ bottom border
15
+ *
16
+ * [LAW:one-source-of-truth] The grid is the shape the reference implementation
17
+ * (Python Rich's `box.py`) publishes these glyphs in, so a constant below can
18
+ * be diffed against it character for character. The eighteen named fields this
19
+ * replaced were a second, differently-shaped map of the same territory, and it
20
+ * had drifted: seven of the nineteen constants carried wrong glyphs, and the
21
+ * field named `mid` held the reference's line 3 while the reference's `mid_*`
22
+ * is line 4 — so reading the reference name-for-name swapped a separator for a
23
+ * content row. Named row fields are gone for that reason; a row is reached by
24
+ * what it is (`getRow`, `getContentChars`), never by a name that can be
25
+ * mismatched to a line.
26
+ */
27
+ import { Segment } from "./segment.js";
28
+ import type { Style } from "./style.js";
29
+ /** A rule spanning the table: the top and bottom borders, and every separator. */
30
+ export interface EdgeChars {
31
+ left: string;
32
+ horizontal: string;
33
+ cross: string;
34
+ right: string;
35
+ }
36
+ /**
37
+ * The verticals framing a row of cells. A content line of the grid has no
38
+ * horizontal of its own — its fill column is a placeholder the cells occupy.
39
+ */
40
+ export interface ContentChars {
41
+ left: string;
42
+ vertical: string;
43
+ right: string;
44
+ }
45
+ export type RowLevel = "head" | "row" | "foot" | "mid";
46
+ export interface SubstituteOptions {
47
+ asciiOnly?: boolean;
48
+ safe?: boolean;
49
+ }
50
+ export declare class Box {
51
+ readonly top: EdgeChars;
52
+ readonly bottom: EdgeChars;
53
+ private readonly grid;
54
+ private readonly headContent;
55
+ private readonly headSeparator;
56
+ private readonly bodyContent;
57
+ private readonly rowSeparator;
58
+ private readonly footSeparator;
59
+ private readonly footContent;
60
+ /**
61
+ * [LAW:parse-dont-validate] The one crossing between a grid string and box
62
+ * glyphs. A `Box` cannot exist without eight rows of four characters, so no
63
+ * consumer below ever re-checks the shape of the data it reads.
64
+ */
65
+ constructor(grid: string);
66
+ /**
67
+ * Renders the top border row for given column widths.
68
+ */
69
+ getTop(widths: readonly number[], style?: Style, edge?: boolean): Segment[];
70
+ /**
71
+ * Renders the separator drawn *above* a row at `level` — the head separator
72
+ * under the header, the row separator between body rows, the foot separator
73
+ * above the footer.
74
+ */
75
+ getRow(widths: readonly number[], level: RowLevel, style?: Style, edge?: boolean): Segment[];
76
+ /**
77
+ * The verticals that frame a content row at `level` — the counterpart to
78
+ * `getRow`, which draws the separator between two such rows.
79
+ */
80
+ getContentChars(level: RowLevel): ContentChars;
81
+ /**
82
+ * Renders the bottom border row.
83
+ */
84
+ getBottom(widths: readonly number[], style?: Style, edge?: boolean): Segment[];
85
+ /**
86
+ * The box to draw with when the platform cannot render this one as written.
87
+ *
88
+ * `asciiOnly` gives up a box that spends non-ASCII glyphs for `ASCII`, and
89
+ * leaves the four already-ASCII styles as they are — a caller that chose
90
+ * MARKDOWN or ASCII_DOUBLE_HEAD asked for that frame and it is already
91
+ * drawable, so answering with `ASCII` would trade a frame the terminal
92
+ * supports for a different one it equally supports. `safe` squares off the
93
+ * rounded corners a legacy Windows terminal draws as blanks.
94
+ */
95
+ substitute(options?: SubstituteOptions): Box;
96
+ private safeSubstitute;
97
+ /**
98
+ * The nearest box that spends no special glyphs on a header — what a table
99
+ * with `showHeader: false` draws with. A box whose head row already matches
100
+ * its body is its own answer, so this is the identity for fourteen of the
101
+ * nineteen shipped styles.
102
+ *
103
+ * It stands beside `substitute` rather than joining it because the two ask
104
+ * unrelated questions: `substitute` asks what the *platform* can draw, this
105
+ * asks what the *table* contains. Nothing correlates them, so a caller that
106
+ * wants both wants both, and a shared options bag would only multiply the
107
+ * combinations either one has to reason about. [LAW:no-mode-explosion]
108
+ *
109
+ * [LAW:types-are-the-program] The relation is keyed on the grid, not on
110
+ * object identity as the reference's dict is. A `Box` is wholly determined by
111
+ * its grid — that is what the constructor takes and all eight rows derive
112
+ * from — so two boxes with one grid must answer this alike. Identity keying
113
+ * would say otherwise the moment a box arrived by any route but the shipped
114
+ * constant, and `safeSubstitute` above builds exactly such a box: a fresh
115
+ * instance carrying an unchanged grid.
116
+ */
117
+ plainHeaded(): Box;
118
+ /**
119
+ * [LAW:dataflow-not-control-flow] Every full-width rule the box can draw is
120
+ * this one join; which rule it is arrives as four characters, not a branch.
121
+ * The rule is one segment, as the reference's `get_top` is one string, so
122
+ * its bytes are one styled run.
123
+ */
124
+ private getEdge;
125
+ private getRowChars;
126
+ }
127
+ export declare const ASCII: Box;
128
+ export declare const ASCII2: Box;
129
+ export declare const ASCII_DOUBLE_HEAD: Box;
130
+ export declare const SQUARE: Box;
131
+ export declare const SQUARE_DOUBLE_HEAD: Box;
132
+ export declare const MINIMAL: Box;
133
+ export declare const MINIMAL_HEAVY_HEAD: Box;
134
+ export declare const MINIMAL_DOUBLE_HEAD: Box;
135
+ export declare const SIMPLE: Box;
136
+ export declare const SIMPLE_HEAD: Box;
137
+ export declare const SIMPLE_HEAVY: Box;
138
+ export declare const HORIZONTALS: Box;
139
+ export declare const ROUNDED: Box;
140
+ export declare const HEAVY: Box;
141
+ export declare const HEAVY_EDGE: Box;
142
+ export declare const HEAVY_HEAD: Box;
143
+ export declare const DOUBLE: Box;
144
+ export declare const DOUBLE_EDGE: Box;
145
+ export declare const MARKDOWN: Box;