@promptctl/rich-js 0.24.1 → 0.25.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 +7 -0
- package/dist/core/anchor.d.ts +5 -0
- package/dist/core/anchor.js +11 -4
- package/dist/core/animation.d.ts +175 -10
- package/dist/core/animation.js +283 -15
- package/dist/core/ansi.js +2 -2
- package/dist/core/box.d.ts +7 -7
- package/dist/core/cells.d.ts +23 -13
- package/dist/core/cells.js +58 -48
- package/dist/core/clock.d.ts +2 -2
- package/dist/core/clock.js +6 -4
- package/dist/core/color.d.ts +27 -10
- package/dist/core/color.js +21 -29
- package/dist/core/console.d.ts +28 -17
- package/dist/core/console.js +44 -39
- package/dist/core/easing.d.ts +30 -0
- package/dist/core/easing.js +65 -20
- package/dist/core/emoji.d.ts +1 -1
- package/dist/core/emoji.js +1 -2
- package/dist/core/export-html.js +2 -1
- package/dist/core/export-lines.js +3 -3
- package/dist/core/highlighter.d.ts +9 -8
- package/dist/core/highlighter.js +3 -2
- package/dist/core/json.js +2 -2
- package/dist/core/markup.js +16 -12
- package/dist/core/measure.js +2 -2
- package/dist/core/memo.d.ts +11 -2
- package/dist/core/memo.js +16 -0
- package/dist/core/oklch.js +17 -6
- package/dist/core/osc8.d.ts +1 -1
- package/dist/core/osc8.js +4 -5
- package/dist/core/paint.d.ts +2 -1
- package/dist/core/paint.js +4 -3
- package/dist/core/pattern.d.ts +21 -0
- package/dist/core/pattern.js +21 -0
- package/dist/core/pretty.js +9 -10
- package/dist/core/protocol.d.ts +100 -39
- package/dist/core/protocol.js +167 -21
- package/dist/core/render.d.ts +15 -2
- package/dist/core/render.js +25 -5
- package/dist/core/segment.d.ts +13 -5
- package/dist/core/segment.js +23 -9
- package/dist/core/spinnerData.d.ts +1 -1
- package/dist/core/state.d.ts +53 -0
- package/dist/core/state.js +82 -0
- package/dist/core/strip.d.ts +13 -16
- package/dist/core/strip.js +119 -110
- package/dist/core/style.d.ts +47 -14
- package/dist/core/style.js +57 -28
- package/dist/core/text.d.ts +5 -8
- package/dist/core/text.js +116 -124
- package/dist/core/timeline.d.ts +30 -5
- package/dist/core/timeline.js +43 -7
- package/dist/core/tween.d.ts +1 -1
- package/dist/core/tween.js +5 -3
- package/dist/core/view.d.ts +92 -0
- package/dist/core/view.js +83 -0
- package/dist/host/app.d.ts +49 -21
- package/dist/host/app.js +71 -37
- package/dist/host/input.js +3 -4
- package/dist/index.d.ts +24 -12
- package/dist/index.js +9 -4
- package/dist/node/traceback.js +4 -3
- package/dist/renderables/align.d.ts +2 -3
- package/dist/renderables/align.js +0 -3
- package/dist/renderables/change.d.ts +42 -0
- package/dist/renderables/change.js +112 -0
- package/dist/renderables/columns.d.ts +3 -4
- package/dist/renderables/columns.js +2 -7
- package/dist/renderables/constrain.d.ts +2 -3
- package/dist/renderables/constrain.js +0 -3
- package/dist/renderables/effect.d.ts +132 -54
- package/dist/renderables/effect.js +188 -115
- package/dist/renderables/effects.d.ts +122 -11
- package/dist/renderables/effects.js +238 -94
- package/dist/renderables/embed.d.ts +1 -1
- package/dist/renderables/embed.js +16 -8
- package/dist/renderables/flexStrip.d.ts +2 -3
- package/dist/renderables/flexStrip.js +0 -4
- package/dist/renderables/frames.d.ts +35 -54
- package/dist/renderables/frames.js +43 -58
- package/dist/renderables/glyph-effect.d.ts +158 -0
- package/dist/renderables/glyph-effect.js +237 -0
- package/dist/renderables/glyph-effects.d.ts +147 -0
- package/dist/renderables/glyph-effects.js +283 -0
- package/dist/renderables/group.d.ts +3 -4
- package/dist/renderables/group.js +0 -3
- package/dist/renderables/layout.d.ts +12 -12
- package/dist/renderables/layout.js +40 -46
- package/dist/renderables/lifecycle.d.ts +77 -0
- package/dist/renderables/lifecycle.js +132 -0
- package/dist/renderables/list-view.d.ts +4 -5
- package/dist/renderables/list-view.js +9 -13
- package/dist/renderables/live.d.ts +36 -21
- package/dist/renderables/live.js +88 -41
- package/dist/renderables/markdown-parse.d.ts +4 -4
- package/dist/renderables/markdown-parse.js +52 -46
- package/dist/renderables/markdown-url.js +11 -11
- package/dist/renderables/padding.d.ts +4 -5
- package/dist/renderables/padding.js +0 -3
- package/dist/renderables/panel.d.ts +14 -16
- package/dist/renderables/panel.js +6 -14
- package/dist/renderables/progress.d.ts +83 -34
- package/dist/renderables/progress.js +182 -95
- package/dist/renderables/progressBar.d.ts +22 -12
- package/dist/renderables/progressBar.js +32 -9
- package/dist/renderables/prompt.js +3 -2
- package/dist/renderables/python-space.js +2 -1
- package/dist/renderables/scroll.d.ts +50 -0
- package/dist/renderables/scroll.js +106 -0
- package/dist/renderables/spinner.d.ts +35 -14
- package/dist/renderables/spinner.js +65 -25
- package/dist/renderables/status.js +1 -1
- package/dist/renderables/syntax.d.ts +10 -9
- package/dist/renderables/syntax.js +3 -2
- package/dist/renderables/table.d.ts +34 -40
- package/dist/renderables/table.js +41 -63
- package/dist/renderables/traceback.d.ts +2 -2
- package/dist/renderables/traceback.js +6 -5
- package/dist/renderables/tree.d.ts +27 -25
- package/dist/renderables/tree.js +104 -58
- package/dist/renderables/viewport.d.ts +75 -44
- package/dist/renderables/viewport.js +145 -82
- package/dist/template-bindings/animation-funcs.d.ts +78 -9
- package/dist/template-bindings/animation-funcs.js +132 -14
- package/dist/template-bindings/index.d.ts +5 -5
- package/dist/template-bindings/index.js +5 -5
- package/dist/themes/ramp.d.ts +46 -3
- package/dist/themes/ramp.js +61 -4
- package/dist/themes/registry.js +5 -7
- package/dist/widgets/button.js +3 -2
- package/dist/widgets/checkbox.js +4 -3
- package/dist/widgets/dropdown.d.ts +3 -1
- package/dist/widgets/dropdown.js +20 -15
- package/dist/widgets/event-router.js +4 -2
- package/dist/widgets/focus-manager.d.ts +2 -0
- package/dist/widgets/focus-manager.js +6 -4
- package/dist/widgets/hit.d.ts +6 -0
- package/dist/widgets/hit.js +8 -1
- package/dist/widgets/observable.d.ts +23 -0
- package/dist/widgets/observable.js +43 -0
- package/dist/widgets/slider.d.ts +1 -1
- package/dist/widgets/slider.js +12 -3
- package/dist/widgets/static-item.d.ts +2 -3
- package/dist/widgets/static-item.js +0 -3
- package/dist/widgets/text-input.d.ts +28 -32
- package/dist/widgets/text-input.js +360 -346
- package/dist/widgets/themed-widget.js +2 -1
- package/dist/widgets/toggle.js +4 -3
- package/dist/widgets/types.d.ts +10 -4
- package/dist/widgets/types.js +8 -2
- package/dist/widgets/widget-app.d.ts +14 -4
- package/dist/widgets/widget-app.js +36 -7
- package/dist/widgets/widget-base.d.ts +2 -2
- package/dist/widgets/widget-base.js +29 -18
- package/package.json +8 -1
package/dist/core/ansi.js
CHANGED
|
@@ -51,10 +51,10 @@ import { RichText } from "./text.js";
|
|
|
51
51
|
* and one final byte (`\x1b(B`, `\x1b)0`, `\x1b#8`, `\x1bc`). Only the first
|
|
52
52
|
* three become tokens.
|
|
53
53
|
*/
|
|
54
|
-
const
|
|
54
|
+
const escapes = () => /\r|\x1b\[([0-9;:]*)m|\x1b\[([012]?)K|\x1b\[[0-?]*[ -/]*[@-~]|\x1b[\]P_^X][\s\S]*?(?:\x07|\x1b\\|\x9c|$)|\x1b[ -/]*[0-~]/g;
|
|
55
55
|
function* escapeTokens(bytes) {
|
|
56
56
|
let at = 0;
|
|
57
|
-
for (const match of bytes.matchAll(
|
|
57
|
+
for (const match of bytes.matchAll(escapes())) {
|
|
58
58
|
if (match.index > at)
|
|
59
59
|
yield { kind: "text", text: bytes.slice(at, match.index) };
|
|
60
60
|
if (match[0] === "\r")
|
package/dist/core/box.d.ts
CHANGED
|
@@ -28,19 +28,19 @@ import { Segment } from "./segment.js";
|
|
|
28
28
|
import type { Style } from "./style.js";
|
|
29
29
|
/** A rule spanning the table: the top and bottom borders, and every separator. */
|
|
30
30
|
export interface EdgeChars {
|
|
31
|
-
left: string;
|
|
32
|
-
horizontal: string;
|
|
33
|
-
cross: string;
|
|
34
|
-
right: string;
|
|
31
|
+
readonly left: string;
|
|
32
|
+
readonly horizontal: string;
|
|
33
|
+
readonly cross: string;
|
|
34
|
+
readonly right: string;
|
|
35
35
|
}
|
|
36
36
|
/**
|
|
37
37
|
* The verticals framing a row of cells. A content line of the grid has no
|
|
38
38
|
* horizontal of its own — its fill column is a placeholder the cells occupy.
|
|
39
39
|
*/
|
|
40
40
|
export interface ContentChars {
|
|
41
|
-
left: string;
|
|
42
|
-
vertical: string;
|
|
43
|
-
right: string;
|
|
41
|
+
readonly left: string;
|
|
42
|
+
readonly vertical: string;
|
|
43
|
+
readonly right: string;
|
|
44
44
|
}
|
|
45
45
|
export type RowLevel = "head" | "row" | "foot" | "mid";
|
|
46
46
|
export interface SubstituteOptions {
|
package/dist/core/cells.d.ts
CHANGED
|
@@ -96,6 +96,22 @@ export declare function cutCells(text: string, positions: readonly CellCol[]): s
|
|
|
96
96
|
* or less is no budget at all, so the text comes back as one line.
|
|
97
97
|
*/
|
|
98
98
|
export declare function chopCells(text: string, maxWidth: CellCol): string[];
|
|
99
|
+
/**
|
|
100
|
+
* `text` cut into rows in one walk over its clusters, row `i` holding as many
|
|
101
|
+
* whole glyphs as fit in `capOf(i)` cells. A row's first glyph is taken even
|
|
102
|
+
* when it is wider than the budget, overflowing it by its own width, so every
|
|
103
|
+
* row is past the last and a loop over the rows ends by construction.
|
|
104
|
+
*
|
|
105
|
+
* [LAW:single-enforcer] `cellFit` leaves its caller to choose between
|
|
106
|
+
* force-taking and skipping; this is that choice, made once. Made twice, the
|
|
107
|
+
* copies drifted — the textarea's soft wrap force-took, `chopCells` did
|
|
108
|
+
* neither and hung. And the rows are cut in one walk because a row at a time,
|
|
109
|
+
* each segmenting the text again, is quadratic in a long word.
|
|
110
|
+
*/
|
|
111
|
+
export declare function cellRows(text: string, capOf: (row: number) => CellCol): Generator<{
|
|
112
|
+
start: CodePoint;
|
|
113
|
+
end: CodePoint;
|
|
114
|
+
}>;
|
|
99
115
|
/**
|
|
100
116
|
* `line` split at each of `cuts`, in one walk over it. Converting each cut to a
|
|
101
117
|
* code-unit offset on its own rescans the line from the start every time, which
|
|
@@ -110,6 +126,13 @@ export declare function splitAtCells(line: string, cuts: readonly CellCol[]): st
|
|
|
110
126
|
* between two of its code points leaves half a glyph.
|
|
111
127
|
*/
|
|
112
128
|
export declare function graphemes(text: string): string[];
|
|
129
|
+
/**
|
|
130
|
+
* The clusters of `text` from code unit `start` on, lazily, so a walk that
|
|
131
|
+
* stops early pays only for the clusters it reads.
|
|
132
|
+
*/
|
|
133
|
+
export declare function clustersFrom(text: string, start: CodePoint): Generator<string>;
|
|
134
|
+
/** `clustersFrom` backwards: the clusters of `text` before code unit `end`, nearest first. */
|
|
135
|
+
export declare function clustersBefore(text: string, end: CodePoint): Generator<string>;
|
|
113
136
|
/**
|
|
114
137
|
* Returns the largest prefix of `text` whose cell width fits within `cap` cells,
|
|
115
138
|
* cut between grapheme clusters. No padding — the returned string may be
|
|
@@ -132,19 +155,6 @@ export declare function cellFitEnd(text: string, cap: CellCol): string;
|
|
|
132
155
|
* grapheme clusters, and every cluster boundary is a code-point boundary.
|
|
133
156
|
*/
|
|
134
157
|
export declare function cellFitFrom(text: string, startCU: CodePoint, cap: CellCol): CodePoint;
|
|
135
|
-
/**
|
|
136
|
-
* Like `cellFitFrom`, but for any `startCU` inside `text` the result is
|
|
137
|
-
* strictly past it: a glyph too wide for `cap` is force-taken whole and
|
|
138
|
-
* overflows `cap` by its own width. So a `while (i < text.length)` loop driven
|
|
139
|
-
* by this terminates by construction, and the re-brand is honest — both
|
|
140
|
-
* operands land on code-point boundaries, so their max does too.
|
|
141
|
-
*
|
|
142
|
-
* [LAW:single-enforcer] `cellFit` documents that its caller must choose between
|
|
143
|
-
* force-taking and skipping; this is that choice, made once. It was made twice
|
|
144
|
-
* before and the copies drifted — the textarea's soft wrap force-took,
|
|
145
|
-
* `chopCells` did neither and hung.
|
|
146
|
-
*/
|
|
147
|
-
export declare function cellStepFrom(text: string, startCU: CodePoint, cap: CellCol): CodePoint;
|
|
148
158
|
/**
|
|
149
159
|
* The code-unit offset a visual column falls at: the end of the largest
|
|
150
160
|
* prefix of `content` within `cellCol` cells, so a column inside a wide glyph
|
package/dist/core/cells.js
CHANGED
|
@@ -3,11 +3,16 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { codePointWidth, widensToEmoji } from "./cell-widths.js";
|
|
5
5
|
import { Memo } from "./memo.js";
|
|
6
|
+
import { pattern } from "./pattern.js";
|
|
6
7
|
const cellLenMemo = new Memo();
|
|
7
8
|
const ZWJ = 0x200d;
|
|
8
9
|
const VS16 = 0xfe0f;
|
|
9
|
-
const JOINERS = /[\u200d\ufe0f]
|
|
10
|
-
const PRINTABLE_ASCII = /^[ -~]
|
|
10
|
+
const JOINERS = pattern(/[\u200d\ufe0f]/);
|
|
11
|
+
const PRINTABLE_ASCII = pattern(/^[ -~]*$/);
|
|
12
|
+
// Segmented afresh on every call: a cursor's step costs a few microseconds
|
|
13
|
+
// even in 10k code units, and a cache of segmentations would hold native
|
|
14
|
+
// memory for every string it kept.
|
|
15
|
+
const SEGMENTER = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
11
16
|
// Rich's `cell_len` over a run of text: each code point the cells Rich's table
|
|
12
17
|
// gives it, but that a ZWJ takes the code point after it into the glyph before
|
|
13
18
|
// so neither adds a cell, and VS16 widens the last code point measured to two
|
|
@@ -48,7 +53,7 @@ function measure(text) {
|
|
|
48
53
|
if (!JOINERS.test(text))
|
|
49
54
|
return richCells(text);
|
|
50
55
|
let cells = 0;
|
|
51
|
-
for (const { segment } of
|
|
56
|
+
for (const { segment } of SEGMENTER.segment(text))
|
|
52
57
|
cells += richCells(segment);
|
|
53
58
|
return cells;
|
|
54
59
|
}
|
|
@@ -185,14 +190,38 @@ export function cutCells(text, positions) {
|
|
|
185
190
|
export function chopCells(text, maxWidth) {
|
|
186
191
|
if (maxWidth <= 0 || text.length === 0)
|
|
187
192
|
return [text];
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
193
|
+
return Array.from(cellRows(text, () => maxWidth), ({ start, end }) => text.slice(start, end));
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* `text` cut into rows in one walk over its clusters, row `i` holding as many
|
|
197
|
+
* whole glyphs as fit in `capOf(i)` cells. A row's first glyph is taken even
|
|
198
|
+
* when it is wider than the budget, overflowing it by its own width, so every
|
|
199
|
+
* row is past the last and a loop over the rows ends by construction.
|
|
200
|
+
*
|
|
201
|
+
* [LAW:single-enforcer] `cellFit` leaves its caller to choose between
|
|
202
|
+
* force-taking and skipping; this is that choice, made once. Made twice, the
|
|
203
|
+
* copies drifted — the textarea's soft wrap force-took, `chopCells` did
|
|
204
|
+
* neither and hung. And the rows are cut in one walk because a row at a time,
|
|
205
|
+
* each segmenting the text again, is quadratic in a long word.
|
|
206
|
+
*/
|
|
207
|
+
export function* cellRows(text, capOf) {
|
|
208
|
+
let row = 0;
|
|
209
|
+
let start = 0;
|
|
210
|
+
let at = 0;
|
|
211
|
+
let cells = 0;
|
|
212
|
+
for (const glyph of clustersFrom(text, asCodePoint(0))) {
|
|
213
|
+
const width = cellLen(glyph);
|
|
214
|
+
if (at > start && cells + width > capOf(row)) {
|
|
215
|
+
yield { start: asCodePoint(start), end: asCodePoint(at) };
|
|
216
|
+
row += 1;
|
|
217
|
+
start = at;
|
|
218
|
+
cells = 0;
|
|
219
|
+
}
|
|
220
|
+
cells += width;
|
|
221
|
+
at += glyph.length;
|
|
194
222
|
}
|
|
195
|
-
|
|
223
|
+
if (at > start)
|
|
224
|
+
yield { start: asCodePoint(start), end: asCodePoint(at) };
|
|
196
225
|
}
|
|
197
226
|
/**
|
|
198
227
|
* `line` split at each of `cuts`, in one walk over it. Converting each cut to a
|
|
@@ -214,21 +243,6 @@ export function splitAtCells(line, cuts) {
|
|
|
214
243
|
}
|
|
215
244
|
return [...pieces, line.slice(start)];
|
|
216
245
|
}
|
|
217
|
-
// [LAW:no-shared-mutable-globals] Private memos, written only by `segmentsOf`:
|
|
218
|
-
// the segmenter, and the segmentation of the last string walked, so a caller
|
|
219
|
-
// stepping along one string does not re-segment the whole of it every step.
|
|
220
|
-
let segmenter;
|
|
221
|
-
let lastSegmented;
|
|
222
|
-
function graphemeSegmenter() {
|
|
223
|
-
return (segmenter ??= new Intl.Segmenter(undefined, { granularity: "grapheme" }));
|
|
224
|
-
}
|
|
225
|
-
/** The grapheme segmentation of `text`, reusing the last one when it is the same string. */
|
|
226
|
-
function segmentsOf(text) {
|
|
227
|
-
if (lastSegmented?.text !== text) {
|
|
228
|
-
lastSegmented = { text, segments: graphemeSegmenter().segment(text) };
|
|
229
|
-
}
|
|
230
|
-
return lastSegmented.segments;
|
|
231
|
-
}
|
|
232
246
|
/**
|
|
233
247
|
* The grapheme clusters of `text`, in order: the unit `cellLen` of a whole
|
|
234
248
|
* string is the sum of. Summing `cellLen` over code points instead disagrees
|
|
@@ -237,20 +251,33 @@ function segmentsOf(text) {
|
|
|
237
251
|
* between two of its code points leaves half a glyph.
|
|
238
252
|
*/
|
|
239
253
|
export function graphemes(text) {
|
|
254
|
+
// Printable ASCII joins nothing, so each character is a cluster of its own
|
|
255
|
+
// and no segmentation is needed — the same reach `measure` makes.
|
|
256
|
+
if (PRINTABLE_ASCII.test(text))
|
|
257
|
+
return text.split("");
|
|
240
258
|
return Array.from(clustersFrom(text, asCodePoint(0)));
|
|
241
259
|
}
|
|
242
260
|
/**
|
|
243
261
|
* The clusters of `text` from code unit `start` on, lazily, so a walk that
|
|
244
262
|
* stops early pays only for the clusters it reads.
|
|
245
263
|
*/
|
|
246
|
-
function* clustersFrom(text, start) {
|
|
247
|
-
const segments =
|
|
264
|
+
export function* clustersFrom(text, start) {
|
|
265
|
+
const segments = SEGMENTER.segment(text);
|
|
248
266
|
for (let at = start, s = segments.containing(at); s; s = segments.containing(at)) {
|
|
249
267
|
const end = s.index + s.segment.length;
|
|
250
268
|
yield text.slice(at, end);
|
|
251
269
|
at = asCodePoint(end);
|
|
252
270
|
}
|
|
253
271
|
}
|
|
272
|
+
/** `clustersFrom` backwards: the clusters of `text` before code unit `end`, nearest first. */
|
|
273
|
+
export function* clustersBefore(text, end) {
|
|
274
|
+
const segments = SEGMENTER.segment(text);
|
|
275
|
+
for (let at = end; at > 0;) {
|
|
276
|
+
const { index } = segments.containing(at - 1);
|
|
277
|
+
yield text.slice(index, at);
|
|
278
|
+
at = asCodePoint(index);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
254
281
|
/**
|
|
255
282
|
* Returns the largest prefix of `text` whose cell width fits within `cap` cells,
|
|
256
283
|
* cut between grapheme clusters. No padding — the returned string may be
|
|
@@ -265,7 +292,7 @@ export function cellFit(text, cap) {
|
|
|
265
292
|
}
|
|
266
293
|
/** `cellFit` from the other end: the largest suffix of `text` within `cap` cells. */
|
|
267
294
|
export function cellFitEnd(text, cap) {
|
|
268
|
-
return text.slice(text.length - fitLength(
|
|
295
|
+
return text.slice(text.length - fitLength(clustersBefore(text, asCodePoint(text.length)), cap));
|
|
269
296
|
}
|
|
270
297
|
/** The code units of the leading `clusters` that fit within `cap` cells. */
|
|
271
298
|
function fitLength(clusters, cap) {
|
|
@@ -292,22 +319,6 @@ function fitLength(clusters, cap) {
|
|
|
292
319
|
export function cellFitFrom(text, startCU, cap) {
|
|
293
320
|
return asCodePoint(startCU + fitLength(clustersFrom(text, startCU), cap));
|
|
294
321
|
}
|
|
295
|
-
/**
|
|
296
|
-
* Like `cellFitFrom`, but for any `startCU` inside `text` the result is
|
|
297
|
-
* strictly past it: a glyph too wide for `cap` is force-taken whole and
|
|
298
|
-
* overflows `cap` by its own width. So a `while (i < text.length)` loop driven
|
|
299
|
-
* by this terminates by construction, and the re-brand is honest — both
|
|
300
|
-
* operands land on code-point boundaries, so their max does too.
|
|
301
|
-
*
|
|
302
|
-
* [LAW:single-enforcer] `cellFit` documents that its caller must choose between
|
|
303
|
-
* force-taking and skipping; this is that choice, made once. It was made twice
|
|
304
|
-
* before and the copies drifted — the textarea's soft wrap force-took,
|
|
305
|
-
* `chopCells` did neither and hung.
|
|
306
|
-
*/
|
|
307
|
-
export function cellStepFrom(text, startCU, cap) {
|
|
308
|
-
const [first = ""] = clustersFrom(text, startCU);
|
|
309
|
-
return asCodePoint(Math.max(cellFitFrom(text, startCU, cap), startCU + first.length));
|
|
310
|
-
}
|
|
311
322
|
/**
|
|
312
323
|
* The code-unit offset a visual column falls at: the end of the largest
|
|
313
324
|
* prefix of `content` within `cellCol` cells, so a column inside a wide glyph
|
|
@@ -328,9 +339,8 @@ export function nextGrapheme(s, cu) {
|
|
|
328
339
|
}
|
|
329
340
|
/** `nextGrapheme` backwards: the start of the cluster ending at `cu`; 0 at the start. */
|
|
330
341
|
export function prevGrapheme(s, cu) {
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
return asCodePoint(segmentsOf(s).containing(cu - 1).index);
|
|
342
|
+
const [cluster = ""] = clustersBefore(s, cu);
|
|
343
|
+
return asCodePoint(cu - cluster.length);
|
|
334
344
|
}
|
|
335
345
|
/**
|
|
336
346
|
* Where an offset into `s` comes to rest as a cursor: itself when it is a
|
|
@@ -340,7 +350,7 @@ export function prevGrapheme(s, cu) {
|
|
|
340
350
|
*/
|
|
341
351
|
export function graphemeBoundary(s, cu) {
|
|
342
352
|
const at = Math.min(cu, s.length);
|
|
343
|
-
const cluster =
|
|
353
|
+
const cluster = SEGMENTER.segment(s).containing(at);
|
|
344
354
|
return asCodePoint(cluster === undefined || cluster.index === at ? at : cluster.index + cluster.segment.length);
|
|
345
355
|
}
|
|
346
356
|
/**
|
package/dist/core/clock.d.ts
CHANGED
|
@@ -46,8 +46,8 @@ export interface Clock {
|
|
|
46
46
|
* Call `tick` once, when this clock reads `time` — as soon as it can for a
|
|
47
47
|
* time already past — unless the returned function is called first. What
|
|
48
48
|
* a host that knows when its frame next changes (`Segment.nextChange`)
|
|
49
|
-
* waits on. A `time`
|
|
50
|
-
*
|
|
49
|
+
* waits on. A `time` that is not a finite number of seconds throws; one
|
|
50
|
+
* further off than a platform timer can wait is waited for in turns.
|
|
51
51
|
*/
|
|
52
52
|
at(time: number, tick: () => void): Unsubscribe;
|
|
53
53
|
}
|
package/dist/core/clock.js
CHANGED
|
@@ -40,9 +40,8 @@ export function systemClock() {
|
|
|
40
40
|
return () => clearInterval(timer);
|
|
41
41
|
},
|
|
42
42
|
at: (time, tick) => {
|
|
43
|
-
if (!(time
|
|
44
|
-
throw new RangeError(`a clock waits
|
|
45
|
-
}
|
|
43
|
+
if (!Number.isFinite(time))
|
|
44
|
+
throw new RangeError(`a clock waits until a finite number of seconds, not ${time}`);
|
|
46
45
|
// [LAW:single-enforcer] A platform timer is due by a whole millisecond
|
|
47
46
|
// of the event loop's own time, truncated, so it can run while this
|
|
48
47
|
// clock still reads under `time` — by most of a millisecond. The
|
|
@@ -51,9 +50,12 @@ export function systemClock() {
|
|
|
51
50
|
// again for what remains, so `tick` runs when `now()` has reached
|
|
52
51
|
// `time` and not before. A host re-drawing at a frame's end would
|
|
53
52
|
// otherwise draw the old frame again and wait again, every boundary.
|
|
53
|
+
// A wait longer than a timer holds is the same re-arming, one longest
|
|
54
|
+
// timer at a time, so a hold a slowed timeline stretches to months is
|
|
55
|
+
// waited out rather than refused.
|
|
54
56
|
let timer;
|
|
55
57
|
const arm = () => {
|
|
56
|
-
timer = setTimeout(() => (now() < time ? arm() : tick()), Math.ceil(Math.max(0, time - now()) * 1000));
|
|
58
|
+
timer = setTimeout(() => (now() < time ? arm() : tick()), Math.min(LONGEST_INTERVAL * 1000, Math.ceil(Math.max(0, time - now()) * 1000)));
|
|
57
59
|
};
|
|
58
60
|
arm();
|
|
59
61
|
return () => clearTimeout(timer);
|
package/dist/core/color.d.ts
CHANGED
|
@@ -67,14 +67,13 @@ export declare function rgbDistance(a: ColorRgba, b: ColorRgba): number;
|
|
|
67
67
|
*/
|
|
68
68
|
export declare class ColorTable {
|
|
69
69
|
private readonly colors;
|
|
70
|
+
/** Each entry's relative luminance. */
|
|
71
|
+
private readonly luminances;
|
|
70
72
|
readonly firstIndex: number;
|
|
71
73
|
private readonly matchMemo;
|
|
72
74
|
private readonly readableMemo;
|
|
73
|
-
constructor(colors: ColorRgba[], firstIndex?: number);
|
|
75
|
+
constructor(colors: readonly ColorRgba[], firstIndex?: number);
|
|
74
76
|
get(index: number): ColorRgba;
|
|
75
|
-
private luminanceCache;
|
|
76
|
-
/** Each entry's relative luminance, computed once per table. */
|
|
77
|
-
private luminances;
|
|
78
77
|
/** How many entries the table holds (terminal indices `firstIndex`…). */
|
|
79
78
|
get size(): number;
|
|
80
79
|
/**
|
|
@@ -150,13 +149,31 @@ export declare function detectColorSystem(options?: DetectColorOptions): ColorDe
|
|
|
150
149
|
export declare function isDumbTerminal(env: Env): boolean;
|
|
151
150
|
/**
|
|
152
151
|
* Where segments are written, as everything the serializer (`segmentsToString`)
|
|
153
|
-
* needs to know about it: the colour depth SGR is drawn at,
|
|
154
|
-
* hyperlinks are emitted
|
|
155
|
-
*
|
|
152
|
+
* needs to know about it: the colour depth SGR is drawn at, whether OSC 8
|
|
153
|
+
* hyperlinks are emitted, and whether the reader there asked for reduced
|
|
154
|
+
* motion — under which the terminal's own blink is not asked for
|
|
155
|
+
* (`shownOn`, `./render.ts`). The serializer takes this value whole, so a
|
|
156
|
+
* fact the encoding comes to need joins it here rather than the serializer's
|
|
157
|
+
* signature.
|
|
156
158
|
*/
|
|
157
159
|
export interface Destination {
|
|
158
160
|
readonly colorSystem: ColorDepth | null;
|
|
159
161
|
readonly hyperlinks: boolean;
|
|
162
|
+
/**
|
|
163
|
+
* The reader asked for reduced motion. A host draws for it with the same
|
|
164
|
+
* value as `RenderOptions.reducedMotion`, read off this field, so what is
|
|
165
|
+
* drawn and how it is encoded take one preference.
|
|
166
|
+
*/
|
|
167
|
+
readonly reducedMotion: boolean;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* What `resolveDestination` is told about where it writes beyond the colour
|
|
171
|
+
* spec: what detection probes, and the reader's preference for motion, which
|
|
172
|
+
* no spec or terminal reports, so the program hands it in. Absent, everything
|
|
173
|
+
* moves.
|
|
174
|
+
*/
|
|
175
|
+
export interface DestinationOptions extends DetectColorOptions {
|
|
176
|
+
reducedMotion?: boolean;
|
|
160
177
|
}
|
|
161
178
|
/**
|
|
162
179
|
* Resolve a colour spec into a `Destination`.
|
|
@@ -168,7 +185,7 @@ export interface Destination {
|
|
|
168
185
|
* colour preferences, leave them alone. A FORCE_COLOR that forces colour on
|
|
169
186
|
* declares the destination takes escapes, so it keeps links on a pipe too.
|
|
170
187
|
*/
|
|
171
|
-
export declare function resolveDestination(spec: ColorSystemName | ColorDepth | null, options?:
|
|
188
|
+
export declare function resolveDestination(spec: ColorSystemName | ColorDepth | null, options?: DestinationOptions): Destination;
|
|
172
189
|
/**
|
|
173
190
|
* Resolve a colour system name into a `ColorDepth` (or `null` for no color).
|
|
174
191
|
*
|
|
@@ -187,7 +204,7 @@ export declare class ColorSpec {
|
|
|
187
204
|
readonly type: ColorDepth;
|
|
188
205
|
readonly number: number | undefined;
|
|
189
206
|
readonly value: ColorRgba | undefined;
|
|
190
|
-
private
|
|
207
|
+
private readonly downgrades;
|
|
191
208
|
constructor(name: string, type: ColorDepth, number?: number, value?: ColorRgba);
|
|
192
209
|
get isDefault(): boolean;
|
|
193
210
|
/**
|
|
@@ -336,7 +353,7 @@ export declare function resolveTerminal(theme: TerminalTheme | undefined, depth:
|
|
|
336
353
|
* writer rounds to those same indices.
|
|
337
354
|
*/
|
|
338
355
|
export declare function downgradeTable(depth: ColorDepth.EIGHT_BIT | ColorDepth.STANDARD | ColorDepth.WINDOWS): ColorTable;
|
|
339
|
-
export declare const ANSI_COLOR_NAMES: Record<string, number
|
|
356
|
+
export declare const ANSI_COLOR_NAMES: Readonly<Record<string, number>>;
|
|
340
357
|
/**
|
|
341
358
|
* Every word `ColorSpec.parse` accepts as a name rather than a `#hex`,
|
|
342
359
|
* `rgb()` or `color(N)` form: `default` and the named palette entries, which
|
package/dist/core/color.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Terminal color representation, parsing, downgrading, and ANSI code generation.
|
|
3
3
|
*/
|
|
4
|
-
import { Memo } from "./memo.js";
|
|
4
|
+
import { Memo, WeakMemo } from "./memo.js";
|
|
5
5
|
// --- ColorRgba ---
|
|
6
6
|
function hex2(byte) {
|
|
7
7
|
return byte.toString(16).padStart(2, "0");
|
|
@@ -129,23 +129,20 @@ export function rgbDistance(a, b) {
|
|
|
129
129
|
*/
|
|
130
130
|
export class ColorTable {
|
|
131
131
|
colors;
|
|
132
|
+
/** Each entry's relative luminance. */
|
|
133
|
+
luminances;
|
|
132
134
|
firstIndex;
|
|
133
135
|
// Keyed by colours a host computes without end (ramp stops, mixes), so bounded.
|
|
134
136
|
matchMemo = new Memo();
|
|
135
137
|
readableMemo = new Memo();
|
|
136
138
|
constructor(colors, firstIndex = 0) {
|
|
137
139
|
this.colors = colors;
|
|
140
|
+
this.luminances = colors.map(relativeLuminance);
|
|
138
141
|
this.firstIndex = firstIndex;
|
|
139
142
|
}
|
|
140
143
|
get(index) {
|
|
141
144
|
return this.colors[index - this.firstIndex];
|
|
142
145
|
}
|
|
143
|
-
luminanceCache;
|
|
144
|
-
/** Each entry's relative luminance, computed once per table. */
|
|
145
|
-
luminances() {
|
|
146
|
-
this.luminanceCache ??= this.colors.map(relativeLuminance);
|
|
147
|
-
return this.luminanceCache;
|
|
148
|
-
}
|
|
149
146
|
/** How many entries the table holds (terminal indices `firstIndex`…). */
|
|
150
147
|
get size() {
|
|
151
148
|
return this.colors.length;
|
|
@@ -192,7 +189,7 @@ export class ColorTable {
|
|
|
192
189
|
let bestScore = -Infinity;
|
|
193
190
|
for (let i = 0; i < this.colors.length; i++) {
|
|
194
191
|
const c = this.colors[i];
|
|
195
|
-
const lc = this.luminances
|
|
192
|
+
const lc = this.luminances[i];
|
|
196
193
|
const ratio = luminanceRatio(lc, lOn);
|
|
197
194
|
const passes = ratio >= minRatio;
|
|
198
195
|
// A passing entry scores by closeness; a failing one only by contrast,
|
|
@@ -376,17 +373,20 @@ function terminalColorSystem(options) {
|
|
|
376
373
|
* colour preferences, leave them alone. A FORCE_COLOR that forces colour on
|
|
377
374
|
* declares the destination takes escapes, so it keeps links on a pipe too.
|
|
378
375
|
*/
|
|
379
|
-
export function resolveDestination(spec, options) {
|
|
376
|
+
export function resolveDestination(spec, options = {}) {
|
|
377
|
+
const reducedMotion = options.reducedMotion ?? false;
|
|
380
378
|
if (spec !== "auto") {
|
|
381
379
|
return {
|
|
382
380
|
colorSystem: typeof spec === "string" ? resolveColorSystem(spec) : spec,
|
|
383
381
|
hyperlinks: true,
|
|
382
|
+
reducedMotion,
|
|
384
383
|
};
|
|
385
384
|
}
|
|
386
385
|
return {
|
|
387
386
|
colorSystem: detectColorSystem(options),
|
|
388
|
-
hyperlinks: (forcedColorSystem(envOf(options
|
|
389
|
-
terminalColorSystem(options
|
|
387
|
+
hyperlinks: (forcedColorSystem(envOf(options)) ?? null) !== null ||
|
|
388
|
+
terminalColorSystem(options) !== null,
|
|
389
|
+
reducedMotion,
|
|
390
390
|
};
|
|
391
391
|
}
|
|
392
392
|
/**
|
|
@@ -419,7 +419,7 @@ export class ColorSpec {
|
|
|
419
419
|
type;
|
|
420
420
|
number;
|
|
421
421
|
value;
|
|
422
|
-
|
|
422
|
+
downgrades = new Memo();
|
|
423
423
|
constructor(name, type, number, value) {
|
|
424
424
|
this.name = name;
|
|
425
425
|
this.type = type;
|
|
@@ -518,12 +518,7 @@ export class ColorSpec {
|
|
|
518
518
|
return this;
|
|
519
519
|
if (this.type === targetSystem || fidelity(this.type) < fidelity(targetSystem))
|
|
520
520
|
return this;
|
|
521
|
-
|
|
522
|
-
if (cached)
|
|
523
|
-
return cached;
|
|
524
|
-
const result = this.performDowngrade(targetSystem);
|
|
525
|
-
this.downgradeCache.set(targetSystem, result);
|
|
526
|
-
return result;
|
|
521
|
+
return this.downgrades.get(`${targetSystem}`, () => this.performDowngrade(targetSystem));
|
|
527
522
|
}
|
|
528
523
|
/**
|
|
529
524
|
* Resolves to an actual RGB(A) value for any color type.
|
|
@@ -636,11 +631,7 @@ export class ColorSpec {
|
|
|
636
631
|
* `matchOn` rounds to, but for the default, read off the same two tables.
|
|
637
632
|
*/
|
|
638
633
|
static indexed(depth, theme) {
|
|
639
|
-
|
|
640
|
-
indexedMemo.set(theme, byDepth);
|
|
641
|
-
const listed = byDepth.get(depth) ?? listIndexed(depth, theme);
|
|
642
|
-
byDepth.set(depth, listed);
|
|
643
|
-
return listed;
|
|
634
|
+
return indexedMemo.get(theme, () => new Memo()).get(`${depth}`, () => listIndexed(depth, theme));
|
|
644
635
|
}
|
|
645
636
|
/**
|
|
646
637
|
* The spec a slot is written in once its colour moves from `was`, what its
|
|
@@ -689,7 +680,7 @@ function cubeCandidate(value) {
|
|
|
689
680
|
return [ColorSpec.fromAnsi(index), EIGHT_BIT_DOWNGRADE_TABLE.get(index)];
|
|
690
681
|
}
|
|
691
682
|
/** `ColorSpec.indexed`'s list, made once for each theme and depth. */
|
|
692
|
-
const indexedMemo = new
|
|
683
|
+
const indexedMemo = new WeakMemo();
|
|
693
684
|
function listIndexed(depth, theme) {
|
|
694
685
|
const slots = () => Array.from({ length: 16 }, (_, slot) => [new ColorSpec(`color(${slot})`, depth === ColorDepth.WINDOWS ? depth : ColorDepth.STANDARD, slot), theme.ansiColors.get(slot)]);
|
|
695
686
|
switch (depth) {
|
|
@@ -736,11 +727,11 @@ class ColorBlend extends ColorSpec {
|
|
|
736
727
|
}
|
|
737
728
|
}
|
|
738
729
|
// --- Parsing internals ---
|
|
739
|
-
const RGB_RE = /^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)
|
|
740
|
-
const COLOR_NUMBER_RE = /^color\((\d+)\)
|
|
741
|
-
const BLEND_RE = /^blend\((.*)\)
|
|
730
|
+
const RGB_RE = pattern(/^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/);
|
|
731
|
+
const COLOR_NUMBER_RE = pattern(/^color\((\d+)\)$/);
|
|
732
|
+
const BLEND_RE = pattern(/^blend\((.*)\)$/);
|
|
742
733
|
// A decimal as `String(number)` spells one, exponent included; `blend` owns its range.
|
|
743
|
-
const FRACTION_RE = /^\s*(?:\d+(?:\.\d+)?|\.\d+)(?:e[+-]?\d+)?\s
|
|
734
|
+
const FRACTION_RE = pattern(/^\s*(?:\d+(?:\.\d+)?|\.\d+)(?:e[+-]?\d+)?\s*$/);
|
|
744
735
|
function parseSingle(key) {
|
|
745
736
|
if (key === "default" || key === "") {
|
|
746
737
|
return ColorSpec.default();
|
|
@@ -817,7 +808,7 @@ function parseByte(key, digits, field) {
|
|
|
817
808
|
// reference, a template argument — becomes one through the three parsers below,
|
|
818
809
|
// and each is this check at the lengths it admits. Each digit pair is a byte, so
|
|
819
810
|
// the grammar holding is what keeps `ColorRgba`'s channels in range.
|
|
820
|
-
const HEX_DIGITS_RE = /^[0-9a-fA-F]
|
|
811
|
+
const HEX_DIGITS_RE = pattern(/^[0-9a-fA-F]+$/);
|
|
821
812
|
// Each admitted digit count, by the grammar it spells: the message a failure
|
|
822
813
|
// carries is read off the lengths a parser admits, so the two cannot disagree.
|
|
823
814
|
const HEX_GRAMMARS = { 6: "RRGGBB", 8: "RRGGBBAA" };
|
|
@@ -963,6 +954,7 @@ export const WINDOWS_TABLE = new ColorTable(buildWindowsTable());
|
|
|
963
954
|
// Runtime-importing `Palette` from `themes/palette.ts` is safe: that module
|
|
964
955
|
// only `import type`s from `core/color.ts`, so there is no runtime cycle.
|
|
965
956
|
import { Palette } from "../themes/palette.js";
|
|
957
|
+
import { pattern } from "./pattern.js";
|
|
966
958
|
const INTERNAL_DEFAULT_THEME = new TerminalTheme(new ColorRgba(0, 0, 0), new ColorRgba(255, 255, 255), STANDARD_TABLE, new Palette("default", true, new Map()));
|
|
967
959
|
/** The Windows console in its default Campbell scheme: a WINDOWS colour's terminal. */
|
|
968
960
|
const WINDOWS_CONSOLE_THEME = new TerminalTheme(WINDOWS_TABLE.get(0), WINDOWS_TABLE.get(7), WINDOWS_TABLE, new Palette("windows", true, new Map()));
|
package/dist/core/console.d.ts
CHANGED
|
@@ -59,6 +59,15 @@ export interface ConsoleOptions {
|
|
|
59
59
|
* renderable this console draws. Default false.
|
|
60
60
|
*/
|
|
61
61
|
asciiOnly?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* The reader asked for reduced motion: every animation this console draws
|
|
64
|
+
* — through `print`, a `Live` or an `App` — is drawn with its motion taken
|
|
65
|
+
* out, waking its host only where content changes
|
|
66
|
+
* (`RenderOptions.reducedMotion`). A preference of the
|
|
67
|
+
* person at the terminal, which no terminal reports, so the program hands
|
|
68
|
+
* it in; nothing here reads the environment for it. Default false.
|
|
69
|
+
*/
|
|
70
|
+
reducedMotion?: boolean;
|
|
62
71
|
/**
|
|
63
72
|
* Static width (cells). Ignored when `getSize` is provided. Falls back to
|
|
64
73
|
* the `COLUMNS` env var / the bound stream's `columns` / 80.
|
|
@@ -127,24 +136,25 @@ export interface PrintOptions {
|
|
|
127
136
|
}
|
|
128
137
|
export declare class Console {
|
|
129
138
|
private readonly _destination;
|
|
130
|
-
private _getSize;
|
|
139
|
+
private readonly _getSize;
|
|
131
140
|
private readonly _getDatetime;
|
|
132
|
-
private _lastLogTime;
|
|
133
|
-
private _style;
|
|
134
|
-
private _isTerminal;
|
|
141
|
+
private readonly _lastLogTime;
|
|
142
|
+
private readonly _style;
|
|
143
|
+
private readonly _isTerminal;
|
|
135
144
|
private readonly _interactive;
|
|
136
|
-
private _file;
|
|
137
|
-
private _stream;
|
|
138
|
-
private _record;
|
|
139
|
-
private _markup;
|
|
140
|
-
private _highlight;
|
|
141
|
-
private _theme;
|
|
142
|
-
private _onStyleError;
|
|
145
|
+
private readonly _file;
|
|
146
|
+
private readonly _stream;
|
|
147
|
+
private readonly _record;
|
|
148
|
+
private readonly _markup;
|
|
149
|
+
private readonly _highlight;
|
|
150
|
+
private readonly _theme;
|
|
151
|
+
private readonly _onStyleError;
|
|
143
152
|
private readonly _asciiOnly;
|
|
144
|
-
private _highlighter;
|
|
145
|
-
private _recorded;
|
|
146
|
-
private _capture;
|
|
147
|
-
private _liveRegion;
|
|
153
|
+
private readonly _highlighter;
|
|
154
|
+
private readonly _recorded;
|
|
155
|
+
private readonly _capture;
|
|
156
|
+
private readonly _liveRegion;
|
|
157
|
+
private readonly _madeIn;
|
|
148
158
|
constructor(options?: ConsoleOptions);
|
|
149
159
|
get size(): {
|
|
150
160
|
width: number;
|
|
@@ -156,8 +166,9 @@ export declare class Console {
|
|
|
156
166
|
get isInteractive(): boolean;
|
|
157
167
|
get colorSystem(): ColorDepth | null;
|
|
158
168
|
/**
|
|
159
|
-
* Where this console writes: the colour depth it draws at
|
|
160
|
-
* emits OSC 8 hyperlinks,
|
|
169
|
+
* Where this console writes: the colour depth it draws at, whether it
|
|
170
|
+
* emits OSC 8 hyperlinks, and whether its reader asked for reduced motion,
|
|
171
|
+
* overrides applied — the value its output is
|
|
161
172
|
* encoded for, for anything that encodes on its behalf (`Live`).
|
|
162
173
|
*/
|
|
163
174
|
get destination(): Destination;
|