@promptctl/rich-js 0.18.1 → 0.20.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.
- package/README.md +11 -8
- package/dist/core/box.d.ts +3 -1
- package/dist/core/box.js +6 -13
- package/dist/core/cells.d.ts +61 -28
- package/dist/core/cells.js +171 -124
- package/dist/core/clock.d.ts +48 -0
- package/dist/core/clock.js +42 -0
- package/dist/core/color.d.ts +99 -18
- package/dist/core/color.js +334 -146
- package/dist/core/console.d.ts +38 -7
- package/dist/core/console.js +159 -90
- package/dist/core/easing.d.ts +86 -0
- package/dist/core/easing.js +171 -0
- package/dist/core/env.d.ts +16 -0
- package/dist/core/env.js +16 -0
- package/dist/core/export-html.js +1 -4
- package/dist/core/export-lines.d.ts +22 -13
- package/dist/core/export-lines.js +37 -19
- package/dist/core/export-svg.d.ts +49 -0
- package/dist/core/export-svg.js +213 -0
- package/dist/core/fnv1a.d.ts +10 -0
- package/dist/core/fnv1a.js +17 -0
- package/dist/core/highlighter.d.ts +2 -2
- package/dist/core/highlighter.js +113 -47
- package/dist/core/json.js +1 -1
- package/dist/core/markup.d.ts +28 -7
- package/dist/core/markup.js +235 -312
- package/dist/core/measure.d.ts +3 -3
- package/dist/core/measure.js +12 -7
- package/dist/core/memo.d.ts +19 -0
- package/dist/core/memo.js +34 -0
- package/dist/core/noise.d.ts +31 -0
- package/dist/core/noise.js +94 -0
- package/dist/core/osc8.d.ts +6 -6
- package/dist/core/osc8.js +9 -21
- package/dist/core/paint.d.ts +107 -0
- package/dist/core/paint.js +161 -0
- package/dist/core/place.d.ts +1 -1
- package/dist/core/place.js +4 -6
- package/dist/core/pretty.d.ts +126 -2
- package/dist/core/pretty.js +571 -105
- package/dist/core/protocol.d.ts +43 -3
- package/dist/core/protocol.js +23 -2
- package/dist/core/render.d.ts +33 -30
- package/dist/core/render.js +43 -83
- package/dist/core/segment.d.ts +6 -5
- package/dist/core/segment.js +11 -15
- package/dist/core/selection.d.ts +12 -0
- package/dist/core/selection.js +14 -0
- package/dist/core/strip.d.ts +116 -74
- package/dist/core/strip.js +266 -217
- package/dist/core/style.d.ts +27 -7
- package/dist/core/style.js +106 -41
- package/dist/core/text.d.ts +43 -16
- package/dist/core/text.js +180 -59
- package/dist/core/wrap.d.ts +5 -0
- package/dist/core/wrap.js +16 -3
- package/dist/host/app.d.ts +95 -17
- package/dist/host/app.js +201 -61
- package/dist/host/index.d.ts +4 -1
- package/dist/host/index.js +1 -0
- package/dist/host/input.d.ts +84 -0
- package/dist/host/input.js +498 -0
- package/dist/host/terminal-host.d.ts +15 -2
- package/dist/host/terminal-host.js +2 -0
- package/dist/index.d.ts +26 -14
- package/dist/index.js +17 -7
- package/dist/node/prompt.d.ts +13 -0
- package/dist/node/prompt.js +30 -9
- package/dist/node/save.d.ts +4 -3
- package/dist/node/save.js +6 -3
- package/dist/node/terminal-host.d.ts +10 -2
- package/dist/node/terminal-host.js +7 -0
- package/dist/node/traceback.d.ts +2 -1
- package/dist/node/traceback.js +3 -39
- package/dist/renderables/align.js +2 -9
- package/dist/renderables/columns.d.ts +45 -29
- package/dist/renderables/columns.js +142 -103
- package/dist/renderables/constrain.js +6 -12
- package/dist/renderables/effect.d.ts +113 -0
- package/dist/renderables/effect.js +182 -0
- package/dist/renderables/effects.d.ts +252 -0
- package/dist/renderables/effects.js +565 -0
- package/dist/renderables/embed.d.ts +94 -31
- package/dist/renderables/embed.js +141 -45
- package/dist/renderables/group.d.ts +6 -3
- package/dist/renderables/group.js +5 -0
- package/dist/renderables/layout.d.ts +6 -23
- package/dist/renderables/layout.js +24 -113
- package/dist/renderables/list-view.d.ts +42 -0
- package/dist/renderables/list-view.js +78 -0
- package/dist/renderables/live.d.ts +40 -16
- package/dist/renderables/live.js +96 -83
- package/dist/renderables/markdown.d.ts +16 -2
- package/dist/renderables/markdown.js +521 -118
- package/dist/renderables/padding.d.ts +22 -1
- package/dist/renderables/padding.js +30 -24
- package/dist/renderables/panel.d.ts +31 -33
- package/dist/renderables/panel.js +137 -141
- package/dist/renderables/progress.d.ts +110 -16
- package/dist/renderables/progress.js +212 -81
- package/dist/renderables/progressBar.d.ts +24 -4
- package/dist/renderables/progressBar.js +78 -23
- package/dist/renderables/prompt.d.ts +37 -15
- package/dist/renderables/prompt.js +125 -85
- package/dist/renderables/ratio.d.ts +123 -0
- package/dist/renderables/ratio.js +213 -0
- package/dist/renderables/rule.d.ts +3 -2
- package/dist/renderables/rule.js +50 -52
- package/dist/renderables/spinner.d.ts +28 -2
- package/dist/renderables/spinner.js +52 -19
- package/dist/renderables/status.d.ts +13 -7
- package/dist/renderables/status.js +15 -29
- package/dist/renderables/syntax.d.ts +37 -7
- package/dist/renderables/syntax.js +134 -83
- package/dist/renderables/table.d.ts +58 -20
- package/dist/renderables/table.js +373 -197
- package/dist/renderables/traceback.d.ts +5 -4
- package/dist/renderables/traceback.js +239 -68
- package/dist/renderables/tree.d.ts +10 -2
- package/dist/renderables/tree.js +61 -34
- package/dist/renderables/viewport.d.ts +30 -0
- package/dist/renderables/viewport.js +61 -7
- package/dist/template-bindings/color-funcs.d.ts +31 -9
- package/dist/template-bindings/color-funcs.js +40 -14
- package/dist/template-bindings/helpers.d.ts +4 -2
- package/dist/template-bindings/helpers.js +5 -20
- package/dist/template-bindings/index.d.ts +29 -17
- package/dist/template-bindings/index.js +36 -20
- package/dist/template-bindings/palette-funcs.d.ts +3 -1
- package/dist/template-bindings/palette-funcs.js +11 -7
- package/dist/template-bindings/style-funcs.js +4 -2
- package/dist/themes/buildPalette.d.ts +24 -3
- package/dist/themes/buildPalette.js +52 -8
- package/dist/themes/colorMath.d.ts +29 -17
- package/dist/themes/colorMath.js +74 -48
- package/dist/themes/colorRef.d.ts +2 -17
- package/dist/themes/colorRef.js +13 -31
- package/dist/themes/palette.d.ts +24 -0
- package/dist/themes/palette.js +46 -3
- package/dist/themes/ramp.d.ts +21 -34
- package/dist/themes/ramp.js +23 -42
- package/dist/themes/registry.js +39 -28
- package/dist/widgets/button.d.ts +2 -6
- package/dist/widgets/button.js +9 -40
- package/dist/widgets/checkbox.d.ts +2 -5
- package/dist/widgets/checkbox.js +9 -31
- package/dist/widgets/dropdown.d.ts +2 -5
- package/dist/widgets/dropdown.js +17 -51
- package/dist/widgets/event-router.d.ts +31 -40
- package/dist/widgets/event-router.js +52 -483
- package/dist/widgets/index.d.ts +2 -1
- package/dist/widgets/ink.d.ts +22 -0
- package/dist/widgets/ink.js +31 -0
- package/dist/widgets/slider.d.ts +2 -5
- package/dist/widgets/slider.js +11 -34
- package/dist/widgets/text-input.d.ts +19 -10
- package/dist/widgets/text-input.js +156 -147
- package/dist/widgets/themed-widget.d.ts +20 -0
- package/dist/widgets/themed-widget.js +89 -0
- package/dist/widgets/toggle.d.ts +2 -5
- package/dist/widgets/toggle.js +8 -31
- package/dist/widgets/types.d.ts +8 -25
- package/dist/widgets/types.js +8 -0
- package/dist/widgets/widget-app.d.ts +24 -7
- package/dist/widgets/widget-app.js +35 -18
- package/dist/widgets/widget-base.d.ts +2 -0
- package/dist/widgets/widget-base.js +9 -2
- package/package.json +31 -17
package/dist/core/console.d.ts
CHANGED
|
@@ -3,15 +3,26 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { Style, Theme } from "./style.js";
|
|
5
5
|
import { ColorDepth } from "./color.js";
|
|
6
|
-
import type { Destination } from "./color.js";
|
|
6
|
+
import type { ColorSystemName, Destination } from "./color.js";
|
|
7
|
+
import type { Env } from "./env.js";
|
|
7
8
|
import type { TerminalTheme } from "./color.js";
|
|
8
9
|
import { type JSONOptions } from "./json.js";
|
|
9
10
|
import type { Highlighter } from "./highlighter.js";
|
|
10
11
|
import { type RuleOptions } from "../renderables/rule.js";
|
|
11
12
|
import type { Height, OverflowMethod, RenderOptions, StyleErrorHandler } from "./protocol.js";
|
|
13
|
+
import type { Unsubscribe } from "./subscription.js";
|
|
12
14
|
export interface ConsoleSink {
|
|
13
15
|
write(chunk: string | Uint8Array): unknown;
|
|
14
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* A frame standing on the terminal under the cursor — a running `Live` — that
|
|
19
|
+
* everything the console writes has to go above. `around` returns the bytes
|
|
20
|
+
* that take the frame off the terminal, write `text`, and paint the frame
|
|
21
|
+
* back under it; the console writes them in one piece.
|
|
22
|
+
*/
|
|
23
|
+
export interface LiveRegion {
|
|
24
|
+
around(text: string): string;
|
|
25
|
+
}
|
|
15
26
|
export interface ConsoleStream extends ConsoleSink {
|
|
16
27
|
readonly isTTY?: boolean;
|
|
17
28
|
readonly columns?: number;
|
|
@@ -23,18 +34,18 @@ export interface ConsoleStream extends ConsoleSink {
|
|
|
23
34
|
* point — injecting a fake terminal needs no adapter at either end.
|
|
24
35
|
*/
|
|
25
36
|
export interface ConsoleEnvironment {
|
|
26
|
-
readonly env:
|
|
37
|
+
readonly env: Env;
|
|
27
38
|
readonly stdout?: ConsoleStream;
|
|
28
39
|
readonly stderr?: ConsoleStream;
|
|
29
40
|
}
|
|
30
41
|
export interface ConsoleOptions {
|
|
31
42
|
/**
|
|
32
|
-
* Color encoding. Accepts a
|
|
33
|
-
* `"ansi"`, `"none"`), a `ColorDepth` enum value (use this for
|
|
34
|
-
* which has no
|
|
43
|
+
* Color encoding. Accepts a `ColorSystemName` (`"auto"`, `"truecolor"`,
|
|
44
|
+
* `"256"`, `"ansi"`, `"none"`), a `ColorDepth` enum value (use this for
|
|
45
|
+
* `WINDOWS`, which has no name), or `null` for no color. Default `"auto"`.
|
|
35
46
|
* Colour only: an explicit depth, `null` included, keeps hyperlinks.
|
|
36
47
|
*/
|
|
37
|
-
colorSystem?:
|
|
48
|
+
colorSystem?: ColorSystemName | ColorDepth | null;
|
|
38
49
|
/**
|
|
39
50
|
* Whether OSC 8 hyperlinks are emitted. Default: what the destination takes
|
|
40
51
|
* — true for an explicit `colorSystem`, detected under `"auto"` (no TTY or
|
|
@@ -72,6 +83,11 @@ export interface ConsoleOptions {
|
|
|
72
83
|
width: number;
|
|
73
84
|
height: number;
|
|
74
85
|
};
|
|
86
|
+
/**
|
|
87
|
+
* The clock `log` stamps each row from, read once per call. Default: the
|
|
88
|
+
* wall clock. Pass a fixed one to make logged output repeatable.
|
|
89
|
+
*/
|
|
90
|
+
getDatetime?: () => Date;
|
|
75
91
|
style?: string | Style;
|
|
76
92
|
forceTerminal?: boolean;
|
|
77
93
|
forceInteractive?: boolean;
|
|
@@ -112,9 +128,11 @@ export interface PrintOptions {
|
|
|
112
128
|
export declare class Console {
|
|
113
129
|
private readonly _destination;
|
|
114
130
|
private _getSize;
|
|
131
|
+
private readonly _getDatetime;
|
|
132
|
+
private _lastLogTime;
|
|
115
133
|
private _style;
|
|
116
134
|
private _isTerminal;
|
|
117
|
-
private
|
|
135
|
+
private readonly _interactive;
|
|
118
136
|
private _file;
|
|
119
137
|
private _stream;
|
|
120
138
|
private _record;
|
|
@@ -126,6 +144,7 @@ export declare class Console {
|
|
|
126
144
|
private _highlighter;
|
|
127
145
|
private _recorded;
|
|
128
146
|
private _capture;
|
|
147
|
+
private _liveRegion;
|
|
129
148
|
constructor(options?: ConsoleOptions);
|
|
130
149
|
get size(): {
|
|
131
150
|
width: number;
|
|
@@ -148,9 +167,16 @@ export declare class Console {
|
|
|
148
167
|
height: Height;
|
|
149
168
|
};
|
|
150
169
|
print(...args: unknown[]): void;
|
|
170
|
+
private _draw;
|
|
151
171
|
log(...args: unknown[]): void;
|
|
152
172
|
rule(title?: string, options?: RuleOptions): void;
|
|
153
173
|
printJson(json: string | object, options?: JSONOptions): void;
|
|
174
|
+
/**
|
|
175
|
+
* Hold `region` under everything this console writes until the returned
|
|
176
|
+
* call lets it go. One region at a time: a second would be erased and
|
|
177
|
+
* painted over by the first, so claiming one while another is held throws.
|
|
178
|
+
*/
|
|
179
|
+
claimLiveRegion(region: LiveRegion): Unsubscribe;
|
|
154
180
|
beginCapture(): void;
|
|
155
181
|
endCapture(): string;
|
|
156
182
|
exportText({ clear }?: {
|
|
@@ -160,6 +186,11 @@ export declare class Console {
|
|
|
160
186
|
theme?: TerminalTheme;
|
|
161
187
|
clear?: boolean;
|
|
162
188
|
}): string;
|
|
189
|
+
exportSvg({ theme, title, clear }?: {
|
|
190
|
+
theme?: TerminalTheme;
|
|
191
|
+
title?: string;
|
|
192
|
+
clear?: boolean;
|
|
193
|
+
}): string;
|
|
163
194
|
private _writeSegments;
|
|
164
195
|
private _write;
|
|
165
196
|
}
|
package/dist/core/console.js
CHANGED
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { Segment } from "./segment.js";
|
|
5
5
|
import { NULL_STYLE, DEFAULT_THEME } from "./style.js";
|
|
6
|
-
import { resolveDestination } from "./color.js";
|
|
6
|
+
import { isDumbTerminal, resolveDestination } from "./color.js";
|
|
7
7
|
import { encodeHtml } from "./export-html.js";
|
|
8
|
+
import { encodeSvg } from "./export-svg.js";
|
|
8
9
|
import { RichText } from "./text.js";
|
|
9
|
-
import {
|
|
10
|
-
import { Pretty } from "./pretty.js";
|
|
10
|
+
import { activeHighlighter, renderStr } from "./markup.js";
|
|
11
|
+
import { Pretty, UNSEEN_DATA_BOUNDS, isExpandable } from "./pretty.js";
|
|
11
12
|
import { JSONRenderable } from "./json.js";
|
|
12
13
|
import { ReprHighlighter, NullHighlighter } from "./highlighter.js";
|
|
13
14
|
// [LAW:one-way-deps] exception: one of the two sanctioned upward edges out of
|
|
@@ -18,6 +19,29 @@ import { Rule } from "../renderables/rule.js";
|
|
|
18
19
|
import { segmentsToString } from "./render.js";
|
|
19
20
|
import { placeBlock } from "./place.js";
|
|
20
21
|
import { isRenderable } from "./protocol.js";
|
|
22
|
+
// [LAW:dataflow-not-control-flow] With no live region claimed, stepping around
|
|
23
|
+
// it is the text itself: every write takes the same path.
|
|
24
|
+
const NO_LIVE_REGION = { around: (text) => text };
|
|
25
|
+
// Rows as the bytes that reach the terminal. Every row but the last is closed
|
|
26
|
+
// by a line break, and the last one when `closed` says so. Crop last, and crop
|
|
27
|
+
// the line-end with the rest: in the reference `end` is the tail of the printed
|
|
28
|
+
// line, so a line cut at the edge loses it too.
|
|
29
|
+
function emit({ rows, closed, cropWidth }) {
|
|
30
|
+
const output = rows.flatMap((row, index) => closed || index < rows.length - 1 ? [...row, Segment.line()] : row);
|
|
31
|
+
return [...Segment.cropLines(output, cropWidth)];
|
|
32
|
+
}
|
|
33
|
+
// The time `log` stamps: local 24-hour `HH:MM:SS`, what the reference's `%X`
|
|
34
|
+
// prints under the C locale a Python program runs in unless it asks for
|
|
35
|
+
// another. The machine's locale never reaches it, so neither does the width
|
|
36
|
+
// an AM/PM suffix would add, and one clock prints one stamp everywhere.
|
|
37
|
+
function logTime(time) {
|
|
38
|
+
// [LAW:no-silent-failure] The clock is the caller's, and an invalid `Date`
|
|
39
|
+
// would otherwise print as `[NaN:NaN:NaN]`.
|
|
40
|
+
if (Number.isNaN(time.getTime()))
|
|
41
|
+
throw new RangeError("Console getDatetime returned an invalid Date");
|
|
42
|
+
const two = (n) => String(n).padStart(2, "0");
|
|
43
|
+
return `[${two(time.getHours())}:${two(time.getMinutes())}:${two(time.getSeconds())}] `;
|
|
44
|
+
}
|
|
21
45
|
// Three of the five justify methods place what a print draws as a block, the
|
|
22
46
|
// way Rich's `print` wraps each renderable in `Align`. The other two leave it
|
|
23
47
|
// where it was drawn: `"default"` asks for no placement, and `"full"` is a way
|
|
@@ -36,23 +60,18 @@ const PLACED_BY = {
|
|
|
36
60
|
// `justify`, `overflow`, `noWrap` and `tabSize` stay behind, as they do in the
|
|
37
61
|
// reference: the print's options set the whole run.
|
|
38
62
|
class TextRun {
|
|
39
|
-
items;
|
|
40
63
|
end;
|
|
64
|
+
text;
|
|
41
65
|
constructor(items, end) {
|
|
42
|
-
this.items = items;
|
|
43
66
|
this.end = end;
|
|
67
|
+
this.text = items.reduce((joined, item) => joined.append(item), new RichText("", { end: "" }));
|
|
44
68
|
}
|
|
45
69
|
*render(options) {
|
|
46
|
-
yield* this.text
|
|
70
|
+
yield* this.text.render(options);
|
|
47
71
|
yield this.end;
|
|
48
72
|
}
|
|
49
73
|
measure(options) {
|
|
50
|
-
return this.text
|
|
51
|
-
}
|
|
52
|
-
// A `Pretty` lays itself out for the width it is offered, so the run is
|
|
53
|
-
// joined per call rather than once.
|
|
54
|
-
text(options) {
|
|
55
|
-
return this.items.reduce((joined, item) => joined.append(item instanceof Pretty ? item.toText(options) : item), new RichText("", { end: "" }));
|
|
74
|
+
return this.text.measure(options);
|
|
56
75
|
}
|
|
57
76
|
}
|
|
58
77
|
// A host with an environment but no streams — what a browser looks like from
|
|
@@ -146,25 +165,6 @@ function resolveGetSize(options, environment, stream) {
|
|
|
146
165
|
// --- Console ---
|
|
147
166
|
/** Shared because it is stateless: highlighting nothing has nothing to own. */
|
|
148
167
|
const NO_HIGHLIGHT = new NullHighlighter();
|
|
149
|
-
/**
|
|
150
|
-
* What `print` bounds a data argument by when the caller said nothing.
|
|
151
|
-
*
|
|
152
|
-
* `print` formats whatever it is handed, so it is the one place that has to
|
|
153
|
-
* assume nothing about the value's size, in any of the three ways a value can
|
|
154
|
-
* be large. Unbounded, `print(buffer)` emits a line per byte,
|
|
155
|
-
* `print(deeplyNested)` descends until the stack gives out, and a single
|
|
156
|
-
* response body assigned to a field arrives in full — each of them a debug line
|
|
157
|
-
* that costs megabytes or hangs the terminal. All three bounds announce
|
|
158
|
-
* themselves in the output (`... +N`, `{...}`, `+N` after the closing quote),
|
|
159
|
-
* so this truncates visibly and never silently. [LAW:no-silent-failure]
|
|
160
|
-
*
|
|
161
|
-
* `maxString` reaches only strings nested inside data; a string argument is
|
|
162
|
-
* printed by the arm above, where the caller asked for that string by name.
|
|
163
|
-
*
|
|
164
|
-
* A caller constructing a `Pretty` has seen their data and gets no defaults;
|
|
165
|
-
* these belong to the convenience path, not to the formatter.
|
|
166
|
-
*/
|
|
167
|
-
const PRINT_DATA_BOUNDS = { maxLength: 100, maxDepth: 16, maxString: 1000 };
|
|
168
168
|
export class Console {
|
|
169
169
|
_destination;
|
|
170
170
|
// [LAW:one-source-of-truth] Size flows through a single function. Static
|
|
@@ -172,9 +172,15 @@ export class Console {
|
|
|
172
172
|
// caller-supplied `getSize` overrides. Every size read in this class goes
|
|
173
173
|
// through `_getSize()` — no second path, no second source to drift.
|
|
174
174
|
_getSize;
|
|
175
|
+
_getDatetime;
|
|
176
|
+
// The stamp the last `log` row was given, which the next row's stamp is
|
|
177
|
+
// compared with: the reference's `LogRender._last_time`, one per console.
|
|
178
|
+
// [LAW:no-ambient-temporal-coupling] "same as the previous row" is a fact
|
|
179
|
+
// about an earlier call, so it is state this console owns, not order luck.
|
|
180
|
+
_lastLogTime = null;
|
|
175
181
|
_style;
|
|
176
182
|
_isTerminal;
|
|
177
|
-
|
|
183
|
+
_interactive;
|
|
178
184
|
_file;
|
|
179
185
|
// [LAW:one-source-of-truth] The resolved output stream, not the `stderr`
|
|
180
186
|
// flag that selected it. Storing the answer instead of the question is what
|
|
@@ -194,6 +200,7 @@ export class Console {
|
|
|
194
200
|
// boolean+string would admit the impossible state "not capturing, buffer
|
|
195
201
|
// non-empty" — `string | null` makes that unrepresentable.
|
|
196
202
|
_capture;
|
|
203
|
+
_liveRegion;
|
|
197
204
|
constructor(options) {
|
|
198
205
|
const environment = options?.environment ?? ambientEnvironment();
|
|
199
206
|
const stream = boundStream(environment, options?.stderr ?? false);
|
|
@@ -211,11 +218,13 @@ export class Console {
|
|
|
211
218
|
hyperlinks: options?.hyperlinks ?? resolved.hyperlinks,
|
|
212
219
|
};
|
|
213
220
|
this._getSize = resolveGetSize(options, environment, stream);
|
|
221
|
+
this._getDatetime = options?.getDatetime ?? (() => new Date());
|
|
214
222
|
// [LAW:no-ambient-temporal-coupling] The theme is assigned before the
|
|
215
223
|
// style, because the console's own style may be one of the theme's names.
|
|
216
224
|
this._theme = options?.theme ?? DEFAULT_THEME;
|
|
217
225
|
this._style = this._theme.resolve(options?.style ?? NULL_STYLE);
|
|
218
|
-
|
|
226
|
+
// Rich's rule: a terminal is interactive unless it is a dumb one.
|
|
227
|
+
this._interactive = options?.forceInteractive ?? (this._isTerminal && !isDumbTerminal(environment.env));
|
|
219
228
|
this._file = options?.file;
|
|
220
229
|
this._record = options?.record ?? false;
|
|
221
230
|
this._markup = options?.markup !== false;
|
|
@@ -225,6 +234,7 @@ export class Console {
|
|
|
225
234
|
this._asciiOnly = options?.asciiOnly ?? false;
|
|
226
235
|
this._recorded = [];
|
|
227
236
|
this._capture = null;
|
|
237
|
+
this._liveRegion = NO_LIVE_REGION;
|
|
228
238
|
}
|
|
229
239
|
// --- Properties ---
|
|
230
240
|
get size() {
|
|
@@ -240,9 +250,7 @@ export class Console {
|
|
|
240
250
|
return this._isTerminal;
|
|
241
251
|
}
|
|
242
252
|
get isInteractive() {
|
|
243
|
-
|
|
244
|
-
return this._forceInteractive;
|
|
245
|
-
return this.isTerminal;
|
|
253
|
+
return this._interactive;
|
|
246
254
|
}
|
|
247
255
|
get colorSystem() {
|
|
248
256
|
return this._destination.colorSystem;
|
|
@@ -282,10 +290,17 @@ export class Console {
|
|
|
282
290
|
theme: this._theme,
|
|
283
291
|
onStyleError: this._onStyleError,
|
|
284
292
|
colorSystem: this._destination.colorSystem,
|
|
293
|
+
markup: this._markup,
|
|
294
|
+
highlight: this._highlight,
|
|
295
|
+
highlighter: this._highlighter,
|
|
285
296
|
};
|
|
286
297
|
}
|
|
287
298
|
// --- Print ---
|
|
288
299
|
print(...args) {
|
|
300
|
+
this._writeSegments(emit(this._draw(args, this.options)));
|
|
301
|
+
}
|
|
302
|
+
// What `print` draws for `args` at `options.maxWidth`.
|
|
303
|
+
_draw(args, options) {
|
|
289
304
|
// Extract options from last arg if it's a PrintOptions
|
|
290
305
|
let opts = {};
|
|
291
306
|
let items;
|
|
@@ -309,53 +324,56 @@ export class Console {
|
|
|
309
324
|
else {
|
|
310
325
|
items = args;
|
|
311
326
|
}
|
|
312
|
-
|
|
313
|
-
|
|
327
|
+
// What a string is read as, wherever in this print it lands: an argument,
|
|
328
|
+
// or a cell or label inside a renderable argument. `print`'s own flags
|
|
329
|
+
// outrank the console's, as Rich's `options.update` has them.
|
|
330
|
+
const strings = {
|
|
331
|
+
markup: opts.markup ?? this._markup,
|
|
332
|
+
highlight: opts.highlight ?? this._highlight,
|
|
333
|
+
highlighter: this._highlighter,
|
|
334
|
+
};
|
|
314
335
|
const sep = opts.sep ?? " ";
|
|
315
336
|
const end = opts.end ?? "\n";
|
|
316
337
|
const softWrap = opts.softWrap ?? false;
|
|
317
338
|
const printStyle = this._theme.resolve(opts.style ?? NULL_STYLE);
|
|
318
339
|
// A print is a column of blocks, and every argument joins one of two kinds.
|
|
319
|
-
// Text — a string, a `RichText`, or
|
|
340
|
+
// Text — a string, a `RichText`, or a scalar — runs together: adjacent text
|
|
320
341
|
// items are one block, joined by `sep` and ended by `end`. Any other
|
|
321
342
|
// renderable is a block of its own that occupies whole lines, so neither
|
|
322
343
|
// `sep` nor `end` ever touches it. That is the reference's split — `end`
|
|
323
344
|
// belongs to text, not to the print — and it is what lets two printed panels
|
|
324
|
-
// stack with no blank line between them.
|
|
325
|
-
//
|
|
326
|
-
//
|
|
327
|
-
//
|
|
328
|
-
//
|
|
329
|
-
//
|
|
330
|
-
//
|
|
345
|
+
// stack with no blank line between them. Data is cut where the reference
|
|
346
|
+
// cuts it: a container (`isExpandable`) is a block, a scalar is text. A
|
|
347
|
+
// container is laid out by `Pretty` across as many lines as it needs, and
|
|
348
|
+
// joined into a run it would start partway along a line and leave its
|
|
349
|
+
// closing bracket where the next item carries on. The line is not closed by
|
|
350
|
+
// asking where the cursor sits: `print("a\n")` is a line and an empty one,
|
|
351
|
+
// here as in the reference and in every other `print`, and only the kind of
|
|
352
|
+
// the item can tell that trailing break from a `Panel`'s. A call with
|
|
353
|
+
// nothing to print is one empty text run, so it still ends the line.
|
|
331
354
|
const blocks = items.length === 0 ? [{ kind: "text", items: [] }] : [];
|
|
332
355
|
for (const item of items) {
|
|
333
|
-
// Four arms, and they are the whole domain. A `RichText` is already text
|
|
334
|
-
//
|
|
335
|
-
//
|
|
356
|
+
// Four arms, and they are the whole domain. A `RichText` is already text.
|
|
357
|
+
// No arm's own `end` reaches the line: `TextRun` joins the run with
|
|
358
|
+
// `append`, which leaves each part's behind, as Rich's `Text(sep).join`
|
|
359
|
+
// does, because the line end of a run is the print's. Any other renderable draws itself,
|
|
336
360
|
// as a block. A string is the only kind of argument that can *contain*
|
|
337
361
|
// markup, so it is the only kind the markup dialect is applied to.
|
|
338
|
-
// Everything else is data
|
|
362
|
+
// Everything else is data — a block when it is a container, text when it
|
|
363
|
+
// is not — and `Pretty` is the single authority on how a
|
|
339
364
|
// JavaScript value displays — `String(value)` was a second, weaker one
|
|
340
365
|
// that answered `[object Object]` for every object and let the markup
|
|
341
366
|
// parser eat it. [LAW:one-source-of-truth]
|
|
342
367
|
let text;
|
|
343
368
|
if (item instanceof RichText) {
|
|
344
|
-
|
|
345
|
-
richText.end = "";
|
|
346
|
-
text = richText;
|
|
369
|
+
text = item;
|
|
347
370
|
}
|
|
348
371
|
else if (isRenderable(item)) {
|
|
349
372
|
blocks.push({ kind: "lines", renderable: item });
|
|
350
373
|
continue;
|
|
351
374
|
}
|
|
352
375
|
else if (typeof item === "string") {
|
|
353
|
-
|
|
354
|
-
richText.end = "";
|
|
355
|
-
if (doHighlight) {
|
|
356
|
-
this._highlighter.highlight(richText);
|
|
357
|
-
}
|
|
358
|
-
text = richText;
|
|
376
|
+
text = renderStr(item, strings);
|
|
359
377
|
}
|
|
360
378
|
else {
|
|
361
379
|
// The highlighter travels with the value. `highlight` and a custom
|
|
@@ -363,18 +381,21 @@ export class Console {
|
|
|
363
381
|
// formatted object the same way they reach a printed string; a `Pretty`
|
|
364
382
|
// left to its own default would outrank both. [LAW:dataflow-not-control-flow]
|
|
365
383
|
// the disabled case is an identity highlighter, not a skipped call.
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
text = new Pretty(item, {
|
|
370
|
-
...PRINT_DATA_BOUNDS,
|
|
371
|
-
highlighter: doHighlight ? this._highlighter : NO_HIGHLIGHT,
|
|
372
|
-
indentGuides: doHighlight,
|
|
384
|
+
const pretty = new Pretty(item, {
|
|
385
|
+
...UNSEEN_DATA_BOUNDS,
|
|
386
|
+
highlighter: activeHighlighter(strings) ?? NO_HIGHLIGHT,
|
|
373
387
|
});
|
|
388
|
+
// A scalar spells the same at every width, so it joins its run as the
|
|
389
|
+
// one text it is.
|
|
390
|
+
if (isExpandable(item)) {
|
|
391
|
+
blocks.push({ kind: "lines", renderable: pretty });
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
text = pretty.toText(options);
|
|
374
395
|
}
|
|
375
396
|
const run = blocks.at(-1);
|
|
376
397
|
if (run?.kind === "text")
|
|
377
|
-
run.items.push(new RichText(sep
|
|
398
|
+
run.items.push(new RichText(sep), text);
|
|
378
399
|
else
|
|
379
400
|
blocks.push({ kind: "text", items: [text] });
|
|
380
401
|
}
|
|
@@ -384,7 +405,8 @@ export class Console {
|
|
|
384
405
|
// at the width, as Rich's does. What `"ignore"` does to a line — no edge,
|
|
385
406
|
// no justify — is `RichText`'s to apply, so it crosses as it was asked for.
|
|
386
407
|
const renderOpts = {
|
|
387
|
-
...
|
|
408
|
+
...options,
|
|
409
|
+
...strings,
|
|
388
410
|
justify: opts.justify === "default" ? undefined : opts.justify,
|
|
389
411
|
overflow: opts.overflow ?? (softWrap ? "ignore" : undefined),
|
|
390
412
|
noWrap: softWrap,
|
|
@@ -404,10 +426,9 @@ export class Console {
|
|
|
404
426
|
// block is closed line by line, as `Align` closes it. Unplaced, a text run
|
|
405
427
|
// is closed exactly where its content — its `end` included — breaks; any
|
|
406
428
|
// other block's lines are closed whether or not it closed them itself: a
|
|
407
|
-
// `Panel` ends in a line break and a bare `
|
|
408
|
-
//
|
|
409
|
-
//
|
|
410
|
-
// line.
|
|
429
|
+
// `Panel` ends in a line break and a bare `ProgressBar` does not (a line
|
|
430
|
+
// fragment — test/seam/line-ends.ts), and both leave the next print at the
|
|
431
|
+
// start of a line.
|
|
411
432
|
const drawBlock = (block) => {
|
|
412
433
|
const renderable = block.kind === "text" ? new TextRun(block.items, terminator) : block.renderable;
|
|
413
434
|
if (align)
|
|
@@ -422,31 +443,56 @@ export class Console {
|
|
|
422
443
|
// Every line end goes through the same writeSegments funnel as the text it
|
|
423
444
|
// ends, so it survives recording — otherwise `exportText` and `exportHtml`
|
|
424
445
|
// would join consecutive prints onto a single line. [LAW:single-enforcer]
|
|
446
|
+
// A block left open carries on along the next block's first line, so the
|
|
447
|
+
// rows are read off the joined output rather than counted per block.
|
|
425
448
|
const output = [];
|
|
449
|
+
let closed = true;
|
|
426
450
|
for (const block of blocks) {
|
|
427
|
-
const
|
|
428
|
-
lines.forEach((line, index) => {
|
|
451
|
+
const drawn = drawBlock(block);
|
|
452
|
+
drawn.lines.forEach((line, index) => {
|
|
429
453
|
output.push(...styleContent(line));
|
|
430
|
-
if (closed || index < lines.length - 1)
|
|
454
|
+
if (drawn.closed || index < drawn.lines.length - 1)
|
|
431
455
|
output.push(Segment.line());
|
|
432
456
|
});
|
|
457
|
+
closed = drawn.closed;
|
|
433
458
|
}
|
|
434
|
-
// Crop last, and crop the line-end with the rest: in the reference `end` is
|
|
435
|
-
// the tail of the printed line, so a line cut at the edge loses it too.
|
|
436
459
|
// Soft wrap turns cropping off whatever `crop` says, because a line it left
|
|
437
460
|
// whole is meant to reach the terminal whole. [LAW:dataflow-not-control-flow]
|
|
438
461
|
// Not cropping is an unbounded width rather than a skipped step — the same
|
|
439
462
|
// spelling `RichText` uses for `"ignore"`.
|
|
440
|
-
const cropWidth = !softWrap && (opts.crop ?? true) ?
|
|
441
|
-
|
|
463
|
+
const cropWidth = !softWrap && (opts.crop ?? true) ? options.maxWidth : Infinity;
|
|
464
|
+
return { rows: Segment.splitLines(output), closed, cropWidth };
|
|
442
465
|
}
|
|
466
|
+
// The reference's `LogRender`: a grid row of two cells, the time and beside it
|
|
467
|
+
// whatever `print` would draw for `args` in the width that is left. A grid row
|
|
468
|
+
// is at least one line tall and always closed, whatever `end` left open
|
|
469
|
+
// inside its cell. The time gives up cells before the content does, so a
|
|
470
|
+
// console narrower than the time still shows every argument. The time takes
|
|
471
|
+
// the console's style alone, as the reference styles only the renderables.
|
|
472
|
+
// A time that reads as the previous row's is left blank at its width, the
|
|
473
|
+
// reference's `omit_repeated_times`, so a burst of rows carries one stamp.
|
|
443
474
|
log(...args) {
|
|
444
|
-
|
|
445
|
-
const
|
|
446
|
-
const
|
|
447
|
-
const
|
|
448
|
-
|
|
449
|
-
|
|
475
|
+
const options = this.options;
|
|
476
|
+
const stamped = logTime(this._getDatetime());
|
|
477
|
+
const repeated = stamped === this._lastLogTime;
|
|
478
|
+
const time = new RichText(stamped, { end: "" });
|
|
479
|
+
time.stylize("log.time");
|
|
480
|
+
const width = Math.min(time.cellLength, options.maxWidth - 1);
|
|
481
|
+
const { rows, cropWidth } = this._draw(args, { ...options, maxWidth: options.maxWidth - width });
|
|
482
|
+
const [stampLine = []] = Segment.splitLines(time.render({ ...options, maxWidth: time.cellLength }));
|
|
483
|
+
const styled = (cell) => [...Segment.applyStyle(cell, this._style)];
|
|
484
|
+
const stamp = styled(Segment.adjustLineLength(stampLine, width));
|
|
485
|
+
const blank = styled([new Segment(" ".repeat(width))]);
|
|
486
|
+
const head = repeated ? blank : stamp;
|
|
487
|
+
const grid = Array.from({ length: Math.max(1, rows.length) }, (_, index) => [
|
|
488
|
+
...(index === 0 ? head : blank),
|
|
489
|
+
...(rows[index] ?? []),
|
|
490
|
+
]);
|
|
491
|
+
// The row is the column wider than its cell; an uncropped cell stays so.
|
|
492
|
+
this._writeSegments(emit({ rows: grid, closed: true, cropWidth: cropWidth + width }));
|
|
493
|
+
// Remembered only once the row is written: a log that threw printed no
|
|
494
|
+
// stamp, so the next row's must show.
|
|
495
|
+
this._lastLogTime = stamped;
|
|
450
496
|
}
|
|
451
497
|
// [LAW:one-source-of-truth] `RuleOptions` is `Rule`'s, not a restatement of
|
|
452
498
|
// it. `console.ts` used to declare its own copy and field-by-field forward
|
|
@@ -466,6 +512,24 @@ export class Console {
|
|
|
466
512
|
: JSONRenderable.fromData(json, options);
|
|
467
513
|
this.print(renderable, { softWrap: true });
|
|
468
514
|
}
|
|
515
|
+
// --- Live region ---
|
|
516
|
+
/**
|
|
517
|
+
* Hold `region` under everything this console writes until the returned
|
|
518
|
+
* call lets it go. One region at a time: a second would be erased and
|
|
519
|
+
* painted over by the first, so claiming one while another is held throws.
|
|
520
|
+
*/
|
|
521
|
+
claimLiveRegion(region) {
|
|
522
|
+
if (this._liveRegion !== NO_LIVE_REGION) {
|
|
523
|
+
throw new Error("A live display is already running on this console; stop it before starting another.");
|
|
524
|
+
}
|
|
525
|
+
this._liveRegion = region;
|
|
526
|
+
// Letting go of a region no longer held — a second call, or after another
|
|
527
|
+
// was claimed — leaves the one that is.
|
|
528
|
+
return () => {
|
|
529
|
+
if (this._liveRegion === region)
|
|
530
|
+
this._liveRegion = NO_LIVE_REGION;
|
|
531
|
+
};
|
|
532
|
+
}
|
|
469
533
|
// --- Capture ---
|
|
470
534
|
// [LAW:one-source-of-truth] Redirect semantics match Python Rich's
|
|
471
535
|
// `Console.capture()`: while a capture is active the underlying target
|
|
@@ -495,12 +559,17 @@ export class Console {
|
|
|
495
559
|
this._recorded = [];
|
|
496
560
|
return html;
|
|
497
561
|
}
|
|
562
|
+
// [LAW:single-enforcer] As `exportHtml`: `encodeSvg` draws, this clears.
|
|
563
|
+
exportSvg({ theme, title = "Rich", clear = true } = {}) {
|
|
564
|
+
const svg = encodeSvg(this._recorded, { theme, title, width: this.width });
|
|
565
|
+
if (clear)
|
|
566
|
+
this._recorded = [];
|
|
567
|
+
return svg;
|
|
568
|
+
}
|
|
498
569
|
// --- Internal ---
|
|
499
570
|
// [LAW:single-enforcer] One encode call per write batch routes through the
|
|
500
|
-
// same `segmentsToString`
|
|
501
|
-
//
|
|
502
|
-
// one pair per segment. Recording captures every non-control segment for
|
|
503
|
-
// replay regardless of how the bytes coalesce.
|
|
571
|
+
// same `segmentsToString` used by `renderToString`. Recording captures every
|
|
572
|
+
// non-control segment for replay.
|
|
504
573
|
_writeSegments(segments) {
|
|
505
574
|
if (this._record) {
|
|
506
575
|
for (const segment of segments) {
|
|
@@ -522,6 +591,6 @@ export class Console {
|
|
|
522
591
|
return;
|
|
523
592
|
}
|
|
524
593
|
const target = this._file ?? defaultSink(this._stream);
|
|
525
|
-
target.write(text);
|
|
594
|
+
target.write(this._liveRegion.around(text));
|
|
526
595
|
}
|
|
527
596
|
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Easing and phase: the one vocabulary for "how far along" — every function
|
|
3
|
+
* here returns progress in [0, 1].
|
|
4
|
+
*
|
|
5
|
+
* Two kinds of curve, composed as `ease(phase(t))`:
|
|
6
|
+
*
|
|
7
|
+
* - A **phase** turns a time `t` (seconds) into raw progress: `once` runs
|
|
8
|
+
* 0 → 1 over a span and holds, `loop` wraps 0 → 1 every period,
|
|
9
|
+
* `pingPong` goes 0 → 1 → 0 every period. A per-cell offset is added to `t`
|
|
10
|
+
* before the phase sees it — `phase(t + offset)` — so the cells of one
|
|
11
|
+
* effect run the same curve shifted in time.
|
|
12
|
+
* - An **ease** reshapes that progress: any `(x) => y` on [0, 1]. The
|
|
13
|
+
* built-ins are CSS's easing functions (CSS Easing Functions Level 1):
|
|
14
|
+
* `linear`, `cubic-bezier()` with its named curves, and `steps()` with its
|
|
15
|
+
* jump terms, plus `sine` for pulses — a half-cosine, so a `pingPong`
|
|
16
|
+
* through it brightens and dims with no corner at either end.
|
|
17
|
+
*
|
|
18
|
+
* [LAW:no-ambient-temporal-coupling] Nothing here reads a clock. `t` is an
|
|
19
|
+
* argument; whoever draws frames owns the clock and samples these at
|
|
20
|
+
* whatever rate it likes, so the same `t` always gives the same progress.
|
|
21
|
+
*
|
|
22
|
+
* [LAW:one-type-per-behavior] An ease is a plain function value. CSS's named
|
|
23
|
+
* curves are four instances of `cubicBezier`, not four implementations, and
|
|
24
|
+
* `ColorRamp` and every effect take the same `Ease`.
|
|
25
|
+
*
|
|
26
|
+
* Tier 0 of `src/core/`: imports nothing, so `themes/` and every later
|
|
27
|
+
* `core/` module reach it downhill. [LAW:one-way-deps]
|
|
28
|
+
*/
|
|
29
|
+
/** Progress in, progress out: a reshaping of [0, 1]. */
|
|
30
|
+
export type Ease = (x: number) => number;
|
|
31
|
+
/** Time in seconds (plus any per-cell offset) in, progress in [0, 1] out. */
|
|
32
|
+
export type Phase = (t: number) => number;
|
|
33
|
+
/**
|
|
34
|
+
* CSS `cubic-bezier(x1, y1, x2, y2)`: the curve from (0, 0) to (1, 1) with
|
|
35
|
+
* those two control points, read as y at a given x. `x1` and `x2` must lie in
|
|
36
|
+
* [0, 1] — as in CSS — which keeps x monotonic in the curve parameter, so
|
|
37
|
+
* every input has exactly one output. `y1` and `y2` may leave [0, 1]; the
|
|
38
|
+
* curve then overshoots, as a CSS "back" curve does.
|
|
39
|
+
*/
|
|
40
|
+
export declare function cubicBezier(x1: number, y1: number, x2: number, y2: number): Ease;
|
|
41
|
+
/**
|
|
42
|
+
* Where a `steps()` curve jumps, in CSS's terms. `jump-start` jumps at 0,
|
|
43
|
+
* `jump-end` at 1, `jump-both` at both, `jump-none` at neither; `start` and
|
|
44
|
+
* `end` are CSS's older spellings of the first two.
|
|
45
|
+
*/
|
|
46
|
+
export type StepPosition = "jump-start" | "jump-end" | "jump-none" | "jump-both" | "start" | "end";
|
|
47
|
+
/**
|
|
48
|
+
* CSS `steps(n, position)`: progress held flat in `n` equal intervals, jumping
|
|
49
|
+
* between them. The output takes `n` distinct values between the jumps
|
|
50
|
+
* `position` names — CSS's step algorithm, so `steps(1, "end")` is 0 until
|
|
51
|
+
* the very end.
|
|
52
|
+
*/
|
|
53
|
+
export declare function steps(n: number, position?: StepPosition): Ease;
|
|
54
|
+
/**
|
|
55
|
+
* Every built-in ease by the name a template spells it with — CSS's keyword
|
|
56
|
+
* names, plus `sine`, plus `step`: the spelling `ColorRamp` used before this
|
|
57
|
+
* vocabulary existed, kept so every template written against it renders the
|
|
58
|
+
* same. It is CSS's `step-end`, the same function value.
|
|
59
|
+
*/
|
|
60
|
+
export declare const EASES: {
|
|
61
|
+
readonly linear: Ease;
|
|
62
|
+
readonly ease: Ease;
|
|
63
|
+
readonly "ease-in": Ease;
|
|
64
|
+
readonly "ease-out": Ease;
|
|
65
|
+
readonly "ease-in-out": Ease;
|
|
66
|
+
readonly "step-start": Ease;
|
|
67
|
+
readonly "step-end": Ease;
|
|
68
|
+
readonly step: Ease;
|
|
69
|
+
readonly sine: Ease;
|
|
70
|
+
};
|
|
71
|
+
export type EaseName = keyof typeof EASES;
|
|
72
|
+
/**
|
|
73
|
+
* The gate a spelled ease crosses. [LAW:parse-dont-validate] — a name in, the
|
|
74
|
+
* function out, so nothing downstream holds a string it must check again.
|
|
75
|
+
* Unknown names throw naming every legal one. [LAW:no-silent-failure]
|
|
76
|
+
*/
|
|
77
|
+
export declare function parseEase(name: string): Ease;
|
|
78
|
+
/** The three phase shapes. Each takes seconds and returns a `Phase`. */
|
|
79
|
+
export declare const Phase: {
|
|
80
|
+
/** 0 before `start`, rising to 1 over `duration` seconds, then held at 1. */
|
|
81
|
+
once(start: number, duration: number): Phase;
|
|
82
|
+
/** 0 → 1 every `period` seconds, wrapping back to 0; negative `t` wraps too. */
|
|
83
|
+
loop(period: number): Phase;
|
|
84
|
+
/** 0 → 1 → 0 every `period` seconds: there in the first half, back in the second. */
|
|
85
|
+
pingPong(period: number): Phase;
|
|
86
|
+
};
|