@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,142 +0,0 @@
1
- /**
2
- * Console — the central orchestrator for rendering styled output to the terminal.
3
- */
4
- import { Style, Theme } from "./style.js";
5
- import { ColorDepth } from "./color.js";
6
- import type { TerminalTheme } from "./color.js";
7
- import { type JSONOptions } from "./json.js";
8
- import type { Highlighter } from "./highlighter.js";
9
- import { type RuleOptions } from "../renderables/rule.js";
10
- import type { RenderOptions, StyleErrorHandler } from "./protocol.js";
11
- export interface ConsoleSink {
12
- write(chunk: string | Uint8Array): unknown;
13
- }
14
- export interface ConsoleStream extends ConsoleSink {
15
- readonly isTTY?: boolean;
16
- readonly columns?: number;
17
- readonly rows?: number;
18
- }
19
- /**
20
- * The host facilities a `Console` consults: an environment map and the two
21
- * standard streams. Node's `process` satisfies this shape as-is, which is the
22
- * point — injecting a fake terminal needs no adapter at either end.
23
- */
24
- export interface ConsoleEnvironment {
25
- readonly env: NodeJS.ProcessEnv;
26
- readonly stdout?: ConsoleStream;
27
- readonly stderr?: ConsoleStream;
28
- }
29
- export interface ConsoleOptions {
30
- /**
31
- * Color encoding. Accepts a string spec (`"auto"`, `"truecolor"`, `"256"`,
32
- * `"ansi"`, `"none"`), a `ColorDepth` enum value (use this for `WINDOWS`,
33
- * which has no string spec), or `null` for no color. Default `"auto"`.
34
- */
35
- colorSystem?: string | ColorDepth | null;
36
- /**
37
- * Static width (cells). Ignored when `getSize` is provided. Falls back to
38
- * the `COLUMNS` env var / the bound stream's `columns` / 80.
39
- */
40
- width?: number;
41
- /**
42
- * Static height (lines). Ignored when `getSize` is provided. Falls back to
43
- * the `LINES` env var / the bound stream's `rows` / 24.
44
- */
45
- height?: number;
46
- /**
47
- * Live size source. When provided, the console reads its width/height from
48
- * this function on every access, overriding any static `width`/`height` and
49
- * the bound stream's dimensions. Pass this when the output target's
50
- * dimensions can change at runtime (e.g. xterm.js in a browser).
51
- *
52
- * [LAW:dataflow-not-control-flow] Live size is a function value the
53
- * console reads, not a branch the call site has to install via
54
- * `Object.defineProperty`.
55
- */
56
- getSize?: () => {
57
- width: number;
58
- height: number;
59
- };
60
- style?: string | Style;
61
- forceTerminal?: boolean;
62
- forceInteractive?: boolean;
63
- stderr?: boolean;
64
- file?: ConsoleSink;
65
- /**
66
- * Host facilities to consult for colour detection, size detection, and the
67
- * default output sink. Defaults to the ambient `process`, or — where there
68
- * is no `process` — an empty environment with no streams. Pass this to drive
69
- * a `Console` deterministically, or to run one against a simulated terminal.
70
- */
71
- environment?: ConsoleEnvironment;
72
- record?: boolean;
73
- markup?: boolean;
74
- highlight?: boolean;
75
- theme?: Theme;
76
- highlighter?: Highlighter;
77
- /**
78
- * Called each time a render resolves a style string it cannot parse — a
79
- * misspelled name, a key the theme lacks — and degrades it to unstyled.
80
- * Calls are per resolution, not per distinct string: a style read twice, or
81
- * redrawn by a refresh, is reported again. Throw from it to make rendering
82
- * strict.
83
- */
84
- onStyleError?: StyleErrorHandler;
85
- }
86
- export interface PrintOptions {
87
- style?: string | Style;
88
- justify?: "default" | "left" | "center" | "right" | "full";
89
- overflow?: "fold" | "crop" | "ellipsis" | "ignore";
90
- highlight?: boolean;
91
- markup?: boolean;
92
- softWrap?: boolean;
93
- crop?: boolean;
94
- end?: string;
95
- sep?: string;
96
- }
97
- export declare class Console {
98
- private _colorSystem;
99
- private _getSize;
100
- private _style;
101
- private _isTerminal;
102
- private _forceInteractive;
103
- private _file;
104
- private _stream;
105
- private _record;
106
- private _markup;
107
- private _highlight;
108
- private _theme;
109
- private _onStyleError;
110
- private _highlighter;
111
- private _recorded;
112
- private _capture;
113
- constructor(options?: ConsoleOptions);
114
- get size(): {
115
- width: number;
116
- height: number;
117
- };
118
- get width(): number;
119
- get height(): number;
120
- get encoding(): string;
121
- get isTerminal(): boolean;
122
- get isInteractive(): boolean;
123
- get colorSystem(): ColorDepth | null;
124
- get file(): ConsoleSink;
125
- get theme(): Theme;
126
- get options(): RenderOptions;
127
- print(...args: unknown[]): void;
128
- log(...args: unknown[]): void;
129
- rule(title?: string, options?: RuleOptions): void;
130
- printJson(json: string | object, options?: JSONOptions): void;
131
- beginCapture(): void;
132
- endCapture(): string;
133
- exportText({ clear }?: {
134
- clear?: boolean;
135
- }): string;
136
- exportHtml({ theme, clear }?: {
137
- theme?: TerminalTheme;
138
- clear?: boolean;
139
- }): string;
140
- private _writeSegments;
141
- private _write;
142
- }
@@ -1,477 +0,0 @@
1
- /**
2
- * Console — the central orchestrator for rendering styled output to the terminal.
3
- */
4
- import { Segment } from "./segment.js";
5
- import { NULL_STYLE, DEFAULT_THEME } from "./style.js";
6
- import { resolveColorSystem } from "./color.js";
7
- import { encodeHtml } from "./export-html.js";
8
- import { RichText } from "./text.js";
9
- import { renderMarkup } from "./markup.js";
10
- import { Pretty } from "./pretty.js";
11
- import { JSONRenderable } from "./json.js";
12
- import { ReprHighlighter, NullHighlighter } from "./highlighter.js";
13
- // [LAW:one-way-deps] exception: one of the two sanctioned upward edges out of
14
- // `core/`, named in CLAUDE.md and pinned by `test/seam/layering.test.ts` — the
15
- // orchestrator reaching down for `Rule`. `RuleOptions` rides the same edge
16
- // rather than being restated here; see `rule()`.
17
- import { Rule } from "../renderables/rule.js";
18
- import { segmentsToString } from "./render.js";
19
- import { isRenderable } from "./protocol.js";
20
- // [LAW:single-enforcer] Color spec → ColorDepth resolution lives in
21
- // `resolveColorSystem`. This helper just normalizes the option shape (string |
22
- // enum | null) into the cached `_colorSystem` field. WINDOWS has no string
23
- // spec; callers reach it via the enum directly.
24
- //
25
- // [LAW:dataflow-not-control-flow] `isTTY` and `env` are forwarded
26
- // unconditionally; `resolveColorSystem` ignores them for non-`"auto"` specs.
27
- // Passing both is what keeps detection inside the console's injected
28
- // environment — left to its own defaults, `detectColorSystem` would consult
29
- // the ambient `process.env` and `process.stdout.isTTY` even for a console
30
- // bound to a different stream.
31
- function resolveOptionColorSystem(spec, detect) {
32
- if (spec === null)
33
- return null;
34
- if (spec === undefined)
35
- return resolveColorSystem("auto", detect);
36
- if (typeof spec === "string")
37
- return resolveColorSystem(spec, detect);
38
- return spec;
39
- }
40
- // A host with an environment but no streams — what a browser looks like from
41
- // here. Frozen so the shared instance cannot be mutated into a fake terminal.
42
- const DETACHED_ENVIRONMENT = Object.freeze({
43
- env: Object.freeze({}),
44
- });
45
- // [LAW:single-enforcer] The only read of the ambient `process` global in this
46
- // module, and the reason every other function below takes its environment as
47
- // an argument. `process` satisfies `ConsoleEnvironment` structurally, so the
48
- // node path needs no adapter; the browser path degrades to a host that answers
49
- // "no streams" rather than throwing at the reference.
50
- function ambientEnvironment() {
51
- return typeof process === "undefined" ? DETACHED_ENVIRONMENT : process;
52
- }
53
- // [LAW:one-source-of-truth] A console talks to exactly one stream, and every
54
- // question it asks the host — are you a terminal, how wide are you, where do
55
- // bytes go — is a question about *that* stream. Selecting it once, here, is
56
- // what makes the three answers agree. When each site re-selected for itself,
57
- // one of them (size) silently didn't, and a `Console({ stderr: true })` keyed
58
- // its colour to stderr and its width to stdout.
59
- function boundStream(environment, useStderr) {
60
- return useStderr ? environment.stderr : environment.stdout;
61
- }
62
- // [LAW:single-enforcer] Effective TTY status of the console's output target is
63
- // computed once, at construction, and cached — an explicit `file` is not a
64
- // terminal, `forceTerminal` says it is regardless, and otherwise the bound
65
- // stream answers. Every input is fixed at construction, so recomputing per
66
- // read could only ever return the same answer twice.
67
- function effectiveIsTTY(options, stream) {
68
- if (options?.forceTerminal)
69
- return true;
70
- if (options?.file)
71
- return false;
72
- return stream?.isTTY ?? false;
73
- }
74
- function terminalSize(environment, stream) {
75
- const cols = environment.env["COLUMNS"];
76
- const lines = environment.env["LINES"];
77
- const w = cols ? parseInt(cols, 10) : (stream?.columns ?? 80);
78
- const h = lines ? parseInt(lines, 10) : (stream?.rows ?? 24);
79
- return { width: w || 80, height: h || 24 };
80
- }
81
- // [LAW:single-enforcer] One trust-boundary check: when no `file:` was
82
- // provided, the console writes to the stream it is bound to — which a browser
83
- // host does not have. Without this, the absence would surface as a `TypeError`
84
- // deep inside `_write`. Centralizing it here keeps both consumers (`file`
85
- // getter, `_write`) behaving identically — no second copy of the check to
86
- // drift.
87
- function defaultSink(stream) {
88
- if (stream === undefined) {
89
- throw new Error("Console: no `file` provided and the environment has no stream to " +
90
- "write to (e.g. running in a browser). Pass `file: hostStream(host)` " +
91
- "or any other ConsoleSink so output has somewhere to go.");
92
- }
93
- return stream;
94
- }
95
- // [LAW:dataflow-not-control-flow] Build one size-reading function at
96
- // construction time from whichever options the caller supplied. The width
97
- // getter is then unconditional — it always calls `_getSize()`. Variability
98
- // lives in the captured closure (which source it consults), not in branches
99
- // the getter has to evaluate on every read.
100
- //
101
- // The all-static and mixed cases get *different* closures: when both
102
- // dimensions are fixed, the returned function is a constant — no
103
- // `terminalSize` call on every read, since neither value can change.
104
- // When at least one dimension is dynamic, the closure consults
105
- // `terminalSize` to fill in the missing side.
106
- function resolveGetSize(options, environment, stream) {
107
- if (options?.getSize)
108
- return options.getSize;
109
- const staticWidth = options?.width;
110
- const staticHeight = options?.height;
111
- if (staticWidth !== undefined && staticHeight !== undefined) {
112
- // [LAW:one-source-of-truth] Freeze the captured object so consumers can't
113
- // mutate `console.size` and silently change every subsequent read.
114
- // Matches the dynamic-closure path (which builds a fresh object per call
115
- // and is therefore implicitly immutable from a sharing standpoint).
116
- const fixed = Object.freeze({ width: staticWidth, height: staticHeight });
117
- return () => fixed;
118
- }
119
- return () => {
120
- const term = terminalSize(environment, stream);
121
- return {
122
- width: staticWidth ?? term.width,
123
- height: staticHeight ?? term.height,
124
- };
125
- };
126
- }
127
- // --- Console ---
128
- /** Shared because it is stateless: highlighting nothing has nothing to own. */
129
- const NO_HIGHLIGHT = new NullHighlighter();
130
- /**
131
- * What `print` bounds a data argument by when the caller said nothing.
132
- *
133
- * `print` formats whatever it is handed, so it is the one place that has to
134
- * assume nothing about the value's size, in any of the three ways a value can
135
- * be large. Unbounded, `print(buffer)` emits a line per byte,
136
- * `print(deeplyNested)` descends until the stack gives out, and a single
137
- * response body assigned to a field arrives in full — each of them a debug line
138
- * that costs megabytes or hangs the terminal. All three bounds announce
139
- * themselves in the output (`... +N`, `{...}`, `+N` after the closing quote),
140
- * so this truncates visibly and never silently. [LAW:no-silent-failure]
141
- *
142
- * `maxString` reaches only strings nested inside data; a string argument is
143
- * printed by the arm above, where the caller asked for that string by name.
144
- *
145
- * A caller constructing a `Pretty` has seen their data and gets no defaults;
146
- * these belong to the convenience path, not to the formatter.
147
- */
148
- const PRINT_DATA_BOUNDS = { maxLength: 100, maxDepth: 16, maxString: 1000 };
149
- export class Console {
150
- _colorSystem;
151
- // [LAW:one-source-of-truth] Size flows through a single function. Static
152
- // `width`/`height` options collapse into a closure that returns them; a
153
- // caller-supplied `getSize` overrides. Every size read in this class goes
154
- // through `_getSize()` — no second path, no second source to drift.
155
- _getSize;
156
- _style;
157
- _isTerminal;
158
- _forceInteractive;
159
- _file;
160
- // [LAW:one-source-of-truth] The resolved output stream, not the `stderr`
161
- // flag that selected it. Storing the answer instead of the question is what
162
- // stops `isTerminal`, `size`, and `file` from re-deriving it three ways.
163
- _stream;
164
- _record;
165
- _markup;
166
- _highlight;
167
- _theme;
168
- _onStyleError;
169
- _highlighter;
170
- _recorded;
171
- // [LAW:types-are-the-program] The capture state is carried in the *type*
172
- // of this field, not in a sentinel value. `null` = not capturing; any
173
- // string (including "") = capturing, this is the buffer. A paired
174
- // boolean+string would admit the impossible state "not capturing, buffer
175
- // non-empty" — `string | null` makes that unrepresentable.
176
- _capture;
177
- constructor(options) {
178
- const environment = options?.environment ?? ambientEnvironment();
179
- const stream = boundStream(environment, options?.stderr ?? false);
180
- this._stream = stream;
181
- this._isTerminal = effectiveIsTTY(options, stream);
182
- this._colorSystem = resolveOptionColorSystem(options?.colorSystem, {
183
- isTTY: this._isTerminal,
184
- env: environment.env,
185
- });
186
- this._getSize = resolveGetSize(options, environment, stream);
187
- // [LAW:no-ambient-temporal-coupling] The theme is assigned before the
188
- // style, because the console's own style may be one of the theme's names.
189
- this._theme = options?.theme ?? DEFAULT_THEME;
190
- this._style = this._theme.resolve(options?.style ?? NULL_STYLE);
191
- this._forceInteractive = options?.forceInteractive;
192
- this._file = options?.file;
193
- this._record = options?.record ?? false;
194
- this._markup = options?.markup !== false;
195
- this._highlight = options?.highlight !== false;
196
- this._highlighter = options?.highlighter ?? new ReprHighlighter();
197
- this._onStyleError = options?.onStyleError;
198
- this._recorded = [];
199
- this._capture = null;
200
- }
201
- // --- Properties ---
202
- get size() {
203
- return this._getSize();
204
- }
205
- get width() {
206
- return this._getSize().width;
207
- }
208
- get height() {
209
- return this._getSize().height;
210
- }
211
- get encoding() {
212
- return "utf-8";
213
- }
214
- get isTerminal() {
215
- return this._isTerminal;
216
- }
217
- get isInteractive() {
218
- if (this._forceInteractive !== undefined)
219
- return this._forceInteractive;
220
- return this.isTerminal;
221
- }
222
- get colorSystem() {
223
- return this._colorSystem;
224
- }
225
- // [LAW:one-source-of-truth] Output target lookup matches `_write`'s:
226
- // explicit `file` wins, otherwise `defaultSink` returns the bound stream
227
- // (or throws clearly when the environment has none). Exposed
228
- // so renderables that bypass the segment pipeline (e.g. Live's raw control
229
- // sequences) still write to the caller's configured stream. Return type is
230
- // the narrow `ConsoleSink` — Live (and any external consumer) only needs
231
- // `.write()`, so the type tells the truth about what the surface guarantees.
232
- get file() {
233
- return this._file ?? defaultSink(this._stream);
234
- }
235
- get theme() {
236
- return this._theme;
237
- }
238
- get options() {
239
- return {
240
- maxWidth: this.width,
241
- isTerminal: this.isTerminal,
242
- encoding: this.encoding,
243
- asciiOnly: false,
244
- theme: this._theme,
245
- onStyleError: this._onStyleError,
246
- colorSystem: this._colorSystem,
247
- };
248
- }
249
- // --- Print ---
250
- print(...args) {
251
- // Extract options from last arg if it's a PrintOptions
252
- let opts = {};
253
- let items;
254
- // The trailing options object is only recognised when something precedes
255
- // it. A lone object is data — `print({ style: "..." })` reads as a value to
256
- // format, and treating it as options would emit an empty line, which is the
257
- // one outcome this method must never produce. Nothing is given up: options
258
- // configure the rendering of content, so a call carrying options and no
259
- // content had nothing to render either way. [LAW:no-silent-failure]
260
- const lastArg = args[args.length - 1];
261
- if (args.length > 1 &&
262
- typeof lastArg === "object" &&
263
- lastArg !== null &&
264
- !isRenderable(lastArg) &&
265
- ("style" in lastArg || "justify" in lastArg || "markup" in lastArg ||
266
- "highlight" in lastArg || "overflow" in lastArg || "end" in lastArg ||
267
- "softWrap" in lastArg || "crop" in lastArg || "sep" in lastArg)) {
268
- opts = lastArg;
269
- items = args.slice(0, -1);
270
- }
271
- else {
272
- items = args;
273
- }
274
- const doMarkup = opts.markup ?? this._markup;
275
- const doHighlight = opts.highlight ?? this._highlight;
276
- const sep = opts.sep ?? " ";
277
- const end = opts.end ?? "\n";
278
- const softWrap = opts.softWrap ?? false;
279
- const printStyle = this._theme.resolve(opts.style ?? NULL_STYLE);
280
- // A print is a column of blocks, and every argument joins one of two kinds.
281
- // Text — a string, a `RichText`, or data — runs together: adjacent text
282
- // items are one block, joined by `sep` and ended by `end`. Any other
283
- // renderable is a block of its own that occupies whole lines, so neither
284
- // `sep` nor `end` ever touches it. That is the reference's split — `end`
285
- // belongs to text, not to the print — and it is what lets two printed panels
286
- // stack with no blank line between them. The one place the reference cuts
287
- // differently is data: it gives a container lines of its own, and here all
288
- // data is text. The line is not closed by asking where the cursor sits:
289
- // `print("a\n")` is a line and an empty one, here as in the reference and
290
- // in every other `print`, and only the kind of the item can tell that
291
- // trailing break from a `Panel`'s. A call with nothing to print is one
292
- // empty text run, so it still ends the line.
293
- const blocks = items.length === 0 ? [{ kind: "text", items: [] }] : [];
294
- for (const item of items) {
295
- // Four arms, and they are the whole domain. A `RichText` is already text;
296
- // it runs as a copy with its own `end` cleared, as a string's is, because
297
- // the line end of a run is the print's. Any other renderable draws itself,
298
- // as a block. A string is the only kind of argument that can *contain*
299
- // markup, so it is the only kind the markup dialect is applied to.
300
- // Everything else is data, and `Pretty` is the single authority on how a
301
- // JavaScript value displays — `String(value)` was a second, weaker one
302
- // that answered `[object Object]` for every object and let the markup
303
- // parser eat it. [LAW:one-source-of-truth]
304
- let text;
305
- if (item instanceof RichText) {
306
- const richText = item.copy();
307
- richText.end = "";
308
- text = richText;
309
- }
310
- else if (isRenderable(item)) {
311
- blocks.push({ kind: "lines", renderable: item });
312
- continue;
313
- }
314
- else if (typeof item === "string") {
315
- const richText = doMarkup ? renderMarkup(item) : new RichText(item);
316
- richText.end = "";
317
- if (doHighlight) {
318
- this._highlighter.highlight(richText);
319
- }
320
- text = richText;
321
- }
322
- else {
323
- // The highlighter travels with the value. `highlight` and a custom
324
- // `highlighter` are console-wide settings, so they have to reach a
325
- // formatted object the same way they reach a printed string; a `Pretty`
326
- // left to its own default would outrank both. [LAW:dataflow-not-control-flow]
327
- // the disabled case is an identity highlighter, not a skipped call.
328
- // Indent guides are styling too, and travel with the same decision —
329
- // the console owns what `highlight` means for everything it emits,
330
- // rather than `Pretty` inferring it back out of the highlighter.
331
- text = new Pretty(item, {
332
- ...PRINT_DATA_BOUNDS,
333
- highlighter: doHighlight ? this._highlighter : NO_HIGHLIGHT,
334
- indentGuides: doHighlight,
335
- });
336
- }
337
- const run = blocks.at(-1);
338
- if (run?.kind === "text")
339
- run.items.push(new RichText(sep, { end: "" }), text);
340
- else
341
- blocks.push({ kind: "text", items: [text] });
342
- }
343
- // [LAW:parse-dont-validate] `"ignore"` is not a way of cutting a line but
344
- // the absence of an edge to cut at: no break, and nothing cut at the width.
345
- // That is what `noWrap` already means to `RichText`, so it crosses into the
346
- // render as `noWrap`, and `overflow` carries only the methods a renderable
347
- // applies. Soft wrap is the same request — the reference defaults it to
348
- // `"ignore"` — which is why the two arrive at one field.
349
- const renderOpts = {
350
- ...this.options,
351
- justify: opts.justify === "default" ? undefined : opts.justify,
352
- overflow: opts.overflow === "ignore" ? undefined : opts.overflow,
353
- noWrap: softWrap || opts.overflow === "ignore",
354
- };
355
- // The print style, then the console's base style, over what each block drew
356
- // and not over the line ends added below: a styled break carries SGR codes
357
- // across the newline.
358
- // [LAW:dataflow-not-control-flow] no style to apply is an empty list, not
359
- // a skipped step.
360
- const styles = [printStyle, this._style].filter((style) => !style.isNull);
361
- const styleContent = (segments) => styles.reduce((styled, style) => Segment.applyStyle(styled, style), segments);
362
- // Every line end goes through the same writeSegments funnel as the text it
363
- // ends, so it survives recording — otherwise `exportText` and `exportHtml`
364
- // would join consecutive prints onto a single line. [LAW:single-enforcer]
365
- // The common default end is "\n" — reuse Segment's cached newline rather
366
- // than allocating one per print; only a non-default end needs a fresh one.
367
- const terminator = end === "\n" ? Segment.line() : new Segment(end);
368
- const output = [];
369
- for (const block of blocks) {
370
- if (block.kind === "text") {
371
- output.push(...styleContent(block.items.flatMap((item) => [...item.render(renderOpts)])), terminator);
372
- }
373
- else {
374
- // A block's lines are closed whether or not it closed them itself: a
375
- // `Panel` ends in a line break and a bare `Spinner` or `ProgressBar`
376
- // does not (they're line fragments, meant to be composed within a
377
- // line — rich-flexstrip-5kf.4cq), and both leave the next print at
378
- // the start of a line.
379
- for (const line of Segment.splitLines(block.renderable.render(renderOpts))) {
380
- output.push(...styleContent(line), Segment.line());
381
- }
382
- }
383
- }
384
- // Crop last, and crop the line-end with the rest: in the reference `end` is
385
- // the tail of the printed line, so a line cut at the edge loses it too.
386
- // Soft wrap turns cropping off whatever `crop` says, because a line it left
387
- // whole is meant to reach the terminal whole. [LAW:dataflow-not-control-flow]
388
- // Not cropping is an unbounded width rather than a skipped step — the same
389
- // spelling `RichText` uses for `noWrap`.
390
- const cropWidth = !softWrap && (opts.crop ?? true) ? this.width : Infinity;
391
- this._writeSegments([...Segment.cropLines(output, cropWidth)]);
392
- }
393
- log(...args) {
394
- // Simple log implementation — adds timestamp
395
- const now = new Date();
396
- const time = now.toLocaleTimeString();
397
- const timeText = new RichText(`[${time}] `, { end: "" });
398
- timeText.stylize("log.time");
399
- this.print(timeText, ...args);
400
- }
401
- // [LAW:one-source-of-truth] `RuleOptions` is `Rule`'s, not a restatement of
402
- // it. `console.ts` used to declare its own copy and field-by-field forward
403
- // into this constructor, which made a second map of one shape — identical
404
- // then, free to drift the moment `Rule` grew an option.
405
- rule(title, options) {
406
- const segments = [...new Rule(title, options).render(this.options)];
407
- this._writeSegments(segments);
408
- }
409
- // [LAW:one-source-of-truth] `JSONRenderable` is the one formatter for JSON and
410
- // `JSONOptions` is its option set; this method only picks the constructor. It
411
- // prints with soft wrap, as the reference does, so a line wider than the
412
- // console reaches the terminal whole and the output stays valid JSON.
413
- printJson(json, options) {
414
- const renderable = typeof json === "string"
415
- ? JSONRenderable.fromString(json, options)
416
- : JSONRenderable.fromData(json, options);
417
- this.print(renderable, { softWrap: true });
418
- }
419
- // --- Capture ---
420
- // [LAW:one-source-of-truth] Redirect semantics match Python Rich's
421
- // `Console.capture()`: while a capture is active the underlying target
422
- // (file / stdout / stderr) receives nothing, and `endCapture()` returns
423
- // the full text that would otherwise have been written. This is not a
424
- // tee — callers wanting both must compose explicitly.
425
- beginCapture() {
426
- this._capture = "";
427
- }
428
- endCapture() {
429
- const result = this._capture ?? "";
430
- this._capture = null;
431
- return result;
432
- }
433
- // --- Export (when record:true) ---
434
- exportText({ clear = true } = {}) {
435
- const text = this._recorded.map((s) => s.text).join("");
436
- if (clear)
437
- this._recorded = [];
438
- return text;
439
- }
440
- // [LAW:single-enforcer] `encodeHtml` draws the picture `exportLines`
441
- // resolves; nothing here interprets a `Style`.
442
- exportHtml({ theme, clear = true } = {}) {
443
- const html = encodeHtml(this._recorded, theme);
444
- if (clear)
445
- this._recorded = [];
446
- return html;
447
- }
448
- // --- Internal ---
449
- // [LAW:single-enforcer] One encode call per write batch routes through the
450
- // same `segmentsToString` tree-coalescer used by `renderToString`. Adjacent
451
- // same-style segments share one SGR open/close pair on the wire instead of
452
- // one pair per segment. Recording captures every non-control segment for
453
- // replay regardless of how the bytes coalesce.
454
- _writeSegments(segments) {
455
- if (this._record) {
456
- for (const segment of segments) {
457
- if (!segment.isControl)
458
- this._recorded.push(segment);
459
- }
460
- }
461
- const encoded = segmentsToString(segments, this._colorSystem);
462
- if (encoded.length > 0)
463
- this._write(encoded);
464
- }
465
- _write(text) {
466
- // [LAW:types-are-the-program] `_capture !== null` is a discriminator
467
- // narrow on `string | null`, not a sentinel comparison — the type
468
- // carries the active/inactive state. The early return is what makes
469
- // this a redirect (not a tee); see beginCapture for semantics.
470
- if (this._capture !== null) {
471
- this._capture += text;
472
- return;
473
- }
474
- const target = this._file ?? defaultSink(this._stream);
475
- target.write(text);
476
- }
477
- }
@@ -1,24 +0,0 @@
1
- /**
2
- * Emoji shortcode support — dictionary, replacement, and renderable.
3
- */
4
- import { Segment } from "./segment.js";
5
- import type { Style } from "./style.js";
6
- import type { Renderable, RenderOptions } from "./protocol.js";
7
- export declare const EMOJI: Record<string, string>;
8
- /**
9
- * Replaces :shortcode: patterns in text with emoji characters.
10
- */
11
- export declare function emojiReplace(text: string, defaultVariant?: "emoji" | "text"): string;
12
- export declare class NoEmoji extends Error {
13
- constructor(name: string);
14
- }
15
- export declare class Emoji implements Renderable {
16
- readonly name: string;
17
- private readonly _char;
18
- private readonly _style;
19
- private readonly _variant;
20
- constructor(name: string, style?: Style, variant?: "emoji" | "text");
21
- toString(): string;
22
- render(_options: RenderOptions): Iterable<Segment>;
23
- static replace(text: string): string;
24
- }