@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,101 +0,0 @@
1
- /**
2
- * Rendering protocol interfaces — Renderable, Measurable, RenderOptions.
3
- * [LAW:one-source-of-truth] These interfaces are the single authority for the rendering contract.
4
- */
5
- import { cellCount } from "./cells.js";
6
- import { DEFAULT_THEME } from "./style.js";
7
- /**
8
- * The style a `string | Style` stands for in this render.
9
- *
10
- * [LAW:single-enforcer] Every renderable resolves a style name here, at render
11
- * time, because only the render knows whose theme it is drawing for. Resolved
12
- * when a renderable was built instead, a name was fixed to the built-in
13
- * defaults before any `Console` could offer its theme, and `new Console({
14
- * theme })` changed nothing.
15
- */
16
- export function getStyle(options, style) {
17
- return (options.theme ?? DEFAULT_THEME).resolve(style);
18
- }
19
- /**
20
- * The options a renderable should work from: the caller's, with `maxWidth`
21
- * parsed into an actual count of cells.
22
- *
23
- * [LAW:parse-dont-validate] The stamp has to travel, which is the whole reason
24
- * this returns options rather than a number. Parsing `options.maxWidth` into a
25
- * local leaves the caller's raw value sitting in `options` for whatever the
26
- * renderable forwards it to — and every renderable forwards it, to a child's
27
- * `render`, to `Measurement.get`, to a nested layout. `Columns` was fixed that
28
- * way first and still threw `Invalid array length` on a NaN width, because it
29
- * handed the unparsed original to `Measurement.get` and got a NaN column count
30
- * back. Replace the field, and nothing downstream can see the number the
31
- * caller actually wrote.
32
- *
33
- * [LAW:single-enforcer] This is the width checkpoint for the render contract.
34
- * There is no second one: a renderable that re-derives its own answer is how
35
- * `Panel`, `Table`, `Tree`, `Columns` and `Layout` came to disagree about what
36
- * a NaN width means — one collapsed to a cell of garbage, two threw, one
37
- * ignored the request and emitted its full natural width.
38
- */
39
- export function withCellWidth(options) {
40
- return { ...options, maxWidth: cellCount(options.maxWidth) };
41
- }
42
- /**
43
- * The options a renderable should *lay out against*: `withCellWidth`, with an
44
- * unbounded offer resolved to the renderable's own natural width.
45
- *
46
- * [LAW:parse-dont-validate] `cellCount` cannot finish the job alone. It floors
47
- * a negative width and NaN to zero from the number alone, but `Infinity` is not
48
- * a quantity it can floor — the only finite answer is "as wide as this
49
- * renderable's content wants", which is a question about the renderable and not
50
- * about the number. So the parse is completed here, where the renderable is in
51
- * hand, and the stamped options travel exactly as `withCellWidth`'s do.
52
- *
53
- * [LAW:single-enforcer] One checkpoint for the whole rule, not one per
54
- * renderable. `Panel`, `Padding`, `Columns` and `Layout` each expand into the
55
- * width they are offered, and each independently reached `" ".repeat(Infinity)`
56
- * — Columns twice over, since its column *count* is derived from the width too
57
- * and `new Array(Infinity)` throws a different error again.
58
- *
59
- * The natural width comes from `measure`, which every one of them already
60
- * implements, and the recursion terminates because `measure` reports content
61
- * rather than the offer: `Panel.measure({maxWidth: Infinity})` is `{9, 9}` for
62
- * nine cells of content and frame, not `{9, Infinity}`.
63
- *
64
- * So this belongs at the top of `render` and never at the top of `measure`:
65
- * `measure` is the method being asked, and asking it through here would ask it
66
- * with itself. `measure` parses with `withCellWidth` and reports a natural
67
- * width of its own — that is the half of the contract that makes this half work.
68
- *
69
- * A `maximum` of `Infinity` means the renderable genuinely cannot answer. Two
70
- * ways in: it wraps a `Renderable` with no `measure`, so nothing in the tree
71
- * knows how wide the content wants to be; or something inside it asked for an
72
- * unbounded width of its own, as a `Table` column declared
73
- * `{ ratio: Infinity }` does. [LAW:no-silent-failure] Both are unanswerable
74
- * rather than zero, and saying so names the cause — where `String.repeat` and
75
- * `new Array` only ever name their own argument.
76
- */
77
- export function withBoundedWidth(options, self) {
78
- const parsed = withCellWidth(options);
79
- if (Number.isFinite(parsed.maxWidth))
80
- return parsed;
81
- const natural = self.measure(parsed).maximum;
82
- if (!Number.isFinite(natural)) {
83
- throw new RangeError("maxWidth is unbounded and this renderable has no natural width to fall " +
84
- "back on: its content does not implement measure(), or something inside " +
85
- "it asked for an unbounded width of its own. Render it at a finite " +
86
- "width, or give the content a measure() that reports a finite maximum.");
87
- }
88
- return { ...parsed, maxWidth: cellCount(natural) };
89
- }
90
- export function isRenderable(obj) {
91
- return (typeof obj === "object" &&
92
- obj !== null &&
93
- "render" in obj &&
94
- typeof obj.render === "function");
95
- }
96
- export function isMeasurable(obj) {
97
- return (typeof obj === "object" &&
98
- obj !== null &&
99
- "measure" in obj &&
100
- typeof obj.measure === "function");
101
- }
@@ -1,69 +0,0 @@
1
- /**
2
- * `renderToString` — stateless one-shot emission of a `Renderable` to a string
3
- * of ANSI-encoded text. Pure function: same inputs produce byte-identical
4
- * output. Does not write to `process.stdout` and does not require a `Console`
5
- * instance.
6
- *
7
- * Note on `colorSystem: "auto"`: the `"auto"` spec resolves via
8
- * `detectColorSystem`, which reads `process.env` and `process.stdout?.isTTY`
9
- * by default. Callers that want a fully deterministic render must either pass
10
- * an explicit `ColorDepth` enum / non-`"auto"` spec, or supply `env` and
11
- * `isTTY` in the options so detection does not consult ambient process state.
12
- *
13
- * [LAW:single-enforcer] The Segment-to-ANSI conversion lives in `segmentsToString`
14
- * and is the single way segments become wire bytes. `Console._writeSegments`,
15
- * `Live.refresh`, and `segmentToString` all delegate here, so terminal output,
16
- * live frames, string export, and single-segment encoding agree by construction.
17
- *
18
- * [LAW:dataflow-not-control-flow] The same pipeline runs every render: collect
19
- * non-control non-empty pieces, partition by SGR-codes (SGR-runs), partition
20
- * each run by link (link-runs), emit one SGR open/close per run with link
21
- * open/close pairs sitting inside. `colorSystem === null` is data: every piece
22
- * is collected with empty SGR-codes *and* no link, so the same pipeline emits
23
- * plain text — no SGR wraps and no OSC 8 hyperlink wraps. NO_COLOR therefore
24
- * strips *all* ANSI escape emission, not just colors.
25
- *
26
- * [LAW:types-are-the-program] Adjacent same-style segments share an SGR wrap
27
- * because the SGR-codes string is the same group key for both — the
28
- * partitioning shape (data) encodes the byte structure; the emit walk is a
29
- * mechanical fold over it.
30
- */
31
- import { ColorDepth } from "./color.js";
32
- import type { Segment } from "./segment.js";
33
- import type { Renderable } from "./protocol.js";
34
- export interface RenderToStringOptions {
35
- /** Cell width to render into. Default 80. */
36
- width?: number;
37
- /**
38
- * Color encoding to emit. Accepts a string spec (`"auto"`, `"truecolor"`,
39
- * `"256"`, `"ansi"`, `"none"`), a `ColorDepth` enum value, or `null` to
40
- * strip *all* ANSI escape emission — SGR colors/attributes *and* OSC 8
41
- * hyperlinks. Default truecolor.
42
- */
43
- colorSystem?: string | ColorDepth | null;
44
- /**
45
- * Environment to consult when `colorSystem` is `"auto"`. Defaults to
46
- * `process.env`. Pass an explicit value to keep rendering deterministic.
47
- */
48
- env?: NodeJS.ProcessEnv;
49
- /**
50
- * Whether output is going to a TTY when `colorSystem` is `"auto"`. Defaults
51
- * to `process.stdout?.isTTY`. Pass an explicit value to keep rendering
52
- * deterministic.
53
- */
54
- isTTY?: boolean;
55
- /** When true, forces `colorSystem` to `null` regardless of the explicit value. */
56
- noColor?: boolean;
57
- }
58
- /**
59
- * Encodes a single segment as ANSI bytes. Equivalent to
60
- * `segmentsToString([segment], colorSystem)` — same SGR / OSC 8 layout.
61
- */
62
- export declare function segmentToString(segment: Segment, colorSystem: ColorDepth | null): string;
63
- /**
64
- * Encodes a sequence of segments as ANSI bytes, coalescing adjacent
65
- * same-SGR segments under a single SGR open/close pair, with OSC 8 link
66
- * pairs nested inside per same-link sub-run.
67
- */
68
- export declare function segmentsToString(segments: Iterable<Segment>, colorSystem: ColorDepth | null): string;
69
- export declare function renderToString(renderable: Renderable, options?: RenderToStringOptions): string;
@@ -1,140 +0,0 @@
1
- /**
2
- * `renderToString` — stateless one-shot emission of a `Renderable` to a string
3
- * of ANSI-encoded text. Pure function: same inputs produce byte-identical
4
- * output. Does not write to `process.stdout` and does not require a `Console`
5
- * instance.
6
- *
7
- * Note on `colorSystem: "auto"`: the `"auto"` spec resolves via
8
- * `detectColorSystem`, which reads `process.env` and `process.stdout?.isTTY`
9
- * by default. Callers that want a fully deterministic render must either pass
10
- * an explicit `ColorDepth` enum / non-`"auto"` spec, or supply `env` and
11
- * `isTTY` in the options so detection does not consult ambient process state.
12
- *
13
- * [LAW:single-enforcer] The Segment-to-ANSI conversion lives in `segmentsToString`
14
- * and is the single way segments become wire bytes. `Console._writeSegments`,
15
- * `Live.refresh`, and `segmentToString` all delegate here, so terminal output,
16
- * live frames, string export, and single-segment encoding agree by construction.
17
- *
18
- * [LAW:dataflow-not-control-flow] The same pipeline runs every render: collect
19
- * non-control non-empty pieces, partition by SGR-codes (SGR-runs), partition
20
- * each run by link (link-runs), emit one SGR open/close per run with link
21
- * open/close pairs sitting inside. `colorSystem === null` is data: every piece
22
- * is collected with empty SGR-codes *and* no link, so the same pipeline emits
23
- * plain text — no SGR wraps and no OSC 8 hyperlink wraps. NO_COLOR therefore
24
- * strips *all* ANSI escape emission, not just colors.
25
- *
26
- * [LAW:types-are-the-program] Adjacent same-style segments share an SGR wrap
27
- * because the SGR-codes string is the same group key for both — the
28
- * partitioning shape (data) encodes the byte structure; the emit walk is a
29
- * mechanical fold over it.
30
- */
31
- import { ColorDepth, resolveColorSystem } from "./color.js";
32
- import { OSC8_CLOSE, osc8Open } from "./osc8.js";
33
- const DEFAULT_WIDTH = 80;
34
- function segmentToPiece(segment, colorSystem) {
35
- if (segment.isControl)
36
- return undefined;
37
- if (segment.text.length === 0)
38
- return undefined;
39
- const style = segment.style;
40
- if (!style || style.isNull || colorSystem === null) {
41
- return { text: segment.text, sgrCodes: "", link: undefined };
42
- }
43
- return {
44
- text: segment.text,
45
- sgrCodes: style.toSgrCodes(colorSystem),
46
- link: style.link,
47
- };
48
- }
49
- /**
50
- * Encodes a single segment as ANSI bytes. Equivalent to
51
- * `segmentsToString([segment], colorSystem)` — same SGR / OSC 8 layout.
52
- */
53
- export function segmentToString(segment, colorSystem) {
54
- return segmentsToString([segment], colorSystem);
55
- }
56
- /**
57
- * Encodes a sequence of segments as ANSI bytes, coalescing adjacent
58
- * same-SGR segments under a single SGR open/close pair, with OSC 8 link
59
- * pairs nested inside per same-link sub-run.
60
- */
61
- export function segmentsToString(segments, colorSystem) {
62
- const pieces = [];
63
- for (const s of segments) {
64
- const p = segmentToPiece(s, colorSystem);
65
- if (p)
66
- pieces.push(p);
67
- }
68
- if (pieces.length === 0)
69
- return "";
70
- // [LAW:dataflow-not-control-flow] One linear chunk accumulator for the
71
- // entire output; SGR / OSC 8 boundaries and piece texts all push into it
72
- // in order. No per-link-run intermediate string, no quadratic `+=` chains.
73
- const parts = [];
74
- let i = 0;
75
- while (i < pieces.length) {
76
- const sgr = pieces[i].sgrCodes;
77
- let j = i + 1;
78
- while (j < pieces.length && pieces[j].sgrCodes === sgr)
79
- j++;
80
- if (sgr.length > 0)
81
- parts.push(`\x1b[${sgr}m`);
82
- let k = i;
83
- while (k < j) {
84
- const link = pieces[k].link;
85
- let l = k + 1;
86
- while (l < j && pieces[l].link === link)
87
- l++;
88
- if (link) {
89
- parts.push(osc8Open(link));
90
- for (let m = k; m < l; m++)
91
- parts.push(pieces[m].text);
92
- parts.push(OSC8_CLOSE);
93
- }
94
- else {
95
- for (let m = k; m < l; m++)
96
- parts.push(pieces[m].text);
97
- }
98
- k = l;
99
- }
100
- if (sgr.length > 0)
101
- parts.push("\x1b[0m");
102
- i = j;
103
- }
104
- return parts.join("");
105
- }
106
- export function renderToString(renderable, options) {
107
- const width = options?.width ?? DEFAULT_WIDTH;
108
- // [LAW:dataflow-not-control-flow] Distinguish "explicit null" from the
109
- // defaulted case. `??` would collapse `null` into the default; `in` would
110
- // accept an explicit `undefined` value as authoritative. Only an explicit
111
- // `null` (or `noColor: true`) strips color; everything else — absent field,
112
- // explicit `undefined` — falls back to truecolor.
113
- // [LAW:single-enforcer] String specs route through `resolveColorSystem`;
114
- // enum/null pass through unchanged.
115
- const rawSpec = options?.colorSystem;
116
- // [LAW:dataflow-not-control-flow] Build the detect options unconditionally;
117
- // `resolveColorSystem` ignores them for non-`"auto"` specs.
118
- const detectOptions = {};
119
- if (options?.env !== undefined)
120
- detectOptions.env = options.env;
121
- if (options?.isTTY !== undefined)
122
- detectOptions.isTTY = options.isTTY;
123
- const colorSystem = options?.noColor
124
- ? null
125
- : rawSpec === undefined
126
- ? ColorDepth.TRUECOLOR
127
- : typeof rawSpec === "string"
128
- ? resolveColorSystem(rawSpec, detectOptions)
129
- : rawSpec;
130
- const renderOptions = {
131
- maxWidth: width,
132
- isTerminal: false,
133
- encoding: "utf-8",
134
- asciiOnly: false,
135
- // [LAW:one-source-of-truth] The depth the segments below are encoded at,
136
- // so a renderable measures what this very call will draw.
137
- colorSystem,
138
- };
139
- return segmentsToString(renderable.render(renderOptions), colorSystem);
140
- }
@@ -1,142 +0,0 @@
1
- /**
2
- * Segment — the atomic rendering unit. Every piece of styled terminal output
3
- * is represented as a Segment: (text, style?, control?).
4
- */
5
- import { type CellCol } from "./cells.js";
6
- import { Style } from "./style.js";
7
- export declare enum ControlType {
8
- BELL = "bell",
9
- CARRIAGE_RETURN = "carriage_return",
10
- HOME = "home",
11
- CLEAR = "clear",
12
- SHOW_CURSOR = "show_cursor",
13
- HIDE_CURSOR = "hide_cursor",
14
- ENABLE_ALT_SCREEN = "enable_alt_screen",
15
- DISABLE_ALT_SCREEN = "disable_alt_screen",
16
- CURSOR_UP = "cursor_up",
17
- CURSOR_DOWN = "cursor_down",
18
- CURSOR_FORWARD = "cursor_forward",
19
- CURSOR_BACKWARD = "cursor_backward",
20
- CURSOR_MOVE_TO_COLUMN = "cursor_move_to_column",
21
- CURSOR_MOVE_TO = "cursor_move_to",
22
- ERASE_IN_LINE = "erase_in_line",
23
- SET_WINDOW_TITLE = "set_window_title"
24
- }
25
- export type ControlCode = [ControlType, ...unknown[]];
26
- export declare class Segment {
27
- readonly text: string;
28
- readonly style: Style | undefined;
29
- readonly control: ControlCode[] | undefined;
30
- constructor(text: string, style?: Style, control?: ControlCode[]);
31
- get cellLength(): number;
32
- get hasText(): boolean;
33
- get isControl(): boolean;
34
- /**
35
- * Splits at a cell position. Returns [left, right].
36
- */
37
- splitCells(position: CellCol): [Segment, Segment];
38
- private static _line;
39
- static line(): Segment;
40
- /**
41
- * Yields segments with combined styles.
42
- */
43
- static applyStyle(segments: Iterable<Segment>, style?: Style, postStyle?: Style): Iterable<Segment>;
44
- /**
45
- * Filters segments by control status.
46
- */
47
- static filterControl(segments: Iterable<Segment>, isControl: boolean): Iterable<Segment>;
48
- /**
49
- * Splits segments at newlines. Yields arrays of segments per line.
50
- */
51
- static splitLines(segments: Iterable<Segment>): Segment[][];
52
- /**
53
- * Adjusts a line of segments to exactly `width` cells.
54
- */
55
- static adjustLineLength(line: Segment[], width: number, style?: Style, pad?: boolean): Segment[];
56
- /**
57
- * Crops a renderable's output so no line exceeds `width`, leaving short lines
58
- * alone and the line structure exactly as it arrived.
59
- *
60
- * [LAW:single-enforcer] `adjustLineLength` is where a width is enforced, but
61
- * it takes one line and a container holding arbitrary content has a stream of
62
- * them. Without this step the container has to trust content to honour the
63
- * offer, and content that ignores it overflows the region — which is how a
64
- * `Layout` leaf and a `Tree` label came to emit forty cells into a one-cell
65
- * offer while `Panel` and `Padding`, which split and adjust, did not.
66
- *
67
- * One segment in, one segment out, shortening text and nothing else — so the
68
- * line structure that arrives is the line structure that leaves. Two earlier
69
- * shapes of this both decided how many lines there were and both were wrong:
70
- * built on `splitLines`, which cannot tell `"abc"` from `"abc\n"`, it invented
71
- * a trailing newline and dropped the row of an empty `Tree` label entirely,
72
- * running its guides into the next row; rebuilt to accumulate lines, it
73
- * dropped the zero-width-but-present line `RichText` emits at an offer of 0,
74
- * and a row split rendered nothing at all. A crop shortens; it does not count.
75
- *
76
- * A wide glyph the edge cuts through leaves a space in the cell it would have
77
- * half-filled, so a cropped line still reaches the edge — the reference's
78
- * `set_cell_size`, which `splitText` already is. An unbounded `width` crops
79
- * nothing, and a segment the crop did not change leaves as itself.
80
- */
81
- static cropLines(segments: Iterable<Segment>, width: number): Iterable<Segment>;
82
- /**
83
- * Returns total cell width of a line. Ignores control segments.
84
- */
85
- static getLineLength(line: Segment[]): number;
86
- /**
87
- * Returns [width, height] of a set of lines.
88
- */
89
- static getShape(lines: Segment[][]): [number, number];
90
- /**
91
- * Composes cells side by side into full lines. Each cell contributes its
92
- * own lines cropped/padded to its declared width; a cell shorter than the
93
- * tallest is filled with blank space so the grid stays aligned. Adjacent
94
- * cells are separated by a gutter of spaces, and each merged row ends with a
95
- * line break. This is the single side-by-side merge used by every horizontal
96
- * layout (Columns grid rows, Layout row splits).
97
- */
98
- static mergeHorizontal(cells: readonly {
99
- readonly lines: Segment[][];
100
- readonly width: number;
101
- }[], gutter?: number): Iterable<Segment>;
102
- /**
103
- * Merges contiguous segments with the same style.
104
- */
105
- static simplify(segments: Iterable<Segment>): Iterable<Segment>;
106
- /**
107
- * Yields segments with links removed.
108
- */
109
- static stripLinks(segments: Iterable<Segment>): Iterable<Segment>;
110
- /**
111
- * Yields segments with all styles removed.
112
- */
113
- static stripStyles(segments: Iterable<Segment>): Iterable<Segment>;
114
- /**
115
- * Yields segments with colors removed but attributes preserved.
116
- */
117
- static removeColor(segments: Iterable<Segment>): Iterable<Segment>;
118
- /**
119
- * Divides segments at cell positions. Yields arrays of segments for each section.
120
- */
121
- static divide(segments: Segment[], cuts: number[]): Segment[][];
122
- /**
123
- * Pads below to fill height.
124
- */
125
- static alignTop(lines: Segment[][], width: number, height: number, style: Style): Segment[][];
126
- /**
127
- * Pads above to fill height. Content at bottom.
128
- */
129
- static alignBottom(lines: Segment[][], width: number, height: number, style: Style): Segment[][];
130
- /**
131
- * Pads above and below. Content in middle.
132
- */
133
- static alignMiddle(lines: Segment[][], width: number, height: number, style: Style): Segment[][];
134
- /**
135
- * Forces lines to exactly width x height.
136
- */
137
- static setShape(lines: Segment[][], width: number, height: number, style?: Style): Segment[][];
138
- /**
139
- * Splits segments into lines and adjusts each to exactly `width` cells.
140
- */
141
- static splitAndCropLines(segments: Iterable<Segment>, width: number, pad?: boolean, includeNewLines?: boolean, style?: Style): Segment[][];
142
- }