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