@promptctl/rich-js 0.24.0 → 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/THIRD-PARTY-NOTICES +29 -1
- package/dist/core/anchor.d.ts +5 -0
- package/dist/core/anchor.js +11 -4
- package/dist/core/animation.d.ts +243 -0
- package/dist/core/animation.js +375 -0
- package/dist/core/ansi.js +2 -2
- package/dist/core/box.d.ts +7 -7
- package/dist/core/cell-widths.d.ts +9 -0
- package/dist/core/cell-widths.js +140 -0
- package/dist/core/cells.d.ts +40 -24
- package/dist/core/cells.js +151 -66
- 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 +48 -43
- package/dist/core/easing.d.ts +140 -10
- package/dist/core/easing.js +328 -50
- 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 +3 -2
- package/dist/core/markup.js +16 -12
- package/dist/core/measure.js +3 -3
- 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/place.js +2 -1
- package/dist/core/pretty.js +11 -12
- package/dist/core/protocol.d.ts +136 -21
- package/dist/core/protocol.js +203 -16
- package/dist/core/render.d.ts +15 -2
- package/dist/core/render.js +27 -6
- package/dist/core/segment.d.ts +31 -6
- package/dist/core/segment.js +68 -53
- 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 +11 -13
- package/dist/core/strip.js +126 -113
- package/dist/core/style.d.ts +47 -14
- package/dist/core/style.js +74 -34
- package/dist/core/text.d.ts +5 -8
- package/dist/core/text.js +133 -133
- package/dist/core/timeline.d.ts +109 -0
- package/dist/core/timeline.js +180 -0
- package/dist/core/tween.d.ts +49 -0
- package/dist/core/tween.js +71 -0
- 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 +72 -38
- package/dist/host/input.js +3 -4
- package/dist/index.d.ts +30 -15
- package/dist/index.js +20 -6
- package/dist/node/traceback.js +4 -3
- package/dist/renderables/change.d.ts +42 -0
- package/dist/renderables/change.js +112 -0
- package/dist/renderables/columns.d.ts +1 -1
- package/dist/renderables/columns.js +3 -3
- package/dist/renderables/constrain.js +2 -1
- package/dist/renderables/effect.d.ts +155 -92
- package/dist/renderables/effect.js +203 -136
- package/dist/renderables/effects.d.ts +137 -24
- package/dist/renderables/effects.js +276 -113
- package/dist/renderables/embed.d.ts +1 -1
- package/dist/renderables/embed.js +18 -9
- package/dist/renderables/flexStrip.js +2 -1
- package/dist/renderables/frames.d.ts +45 -43
- package/dist/renderables/frames.js +57 -45
- 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 +1 -1
- package/dist/renderables/group.js +2 -2
- package/dist/renderables/html-entities.d.ts +8 -0
- package/dist/renderables/html-entities.js +386 -0
- package/dist/renderables/layout.d.ts +10 -9
- package/dist/renderables/layout.js +42 -35
- package/dist/renderables/lifecycle.d.ts +77 -0
- package/dist/renderables/lifecycle.js +132 -0
- package/dist/renderables/list-view.d.ts +2 -2
- package/dist/renderables/list-view.js +11 -8
- package/dist/renderables/live.d.ts +36 -21
- package/dist/renderables/live.js +89 -42
- package/dist/renderables/markdown-parse.d.ts +109 -0
- package/dist/renderables/markdown-parse.js +1427 -0
- package/dist/renderables/markdown-url.d.ts +19 -0
- package/dist/renderables/markdown-url.js +314 -0
- package/dist/renderables/markdown.d.ts +5 -7
- package/dist/renderables/markdown.js +356 -683
- package/dist/renderables/padding.d.ts +7 -20
- package/dist/renderables/padding.js +29 -50
- package/dist/renderables/panel.d.ts +30 -32
- package/dist/renderables/panel.js +65 -109
- package/dist/renderables/progress.d.ts +83 -32
- package/dist/renderables/progress.js +186 -90
- package/dist/renderables/progressBar.d.ts +22 -12
- package/dist/renderables/progressBar.js +33 -10
- package/dist/renderables/prompt.js +3 -2
- package/dist/renderables/python-space.d.ts +13 -0
- package/dist/renderables/python-space.js +25 -0
- package/dist/renderables/scroll.d.ts +50 -0
- package/dist/renderables/scroll.js +106 -0
- package/dist/renderables/spinner.d.ts +57 -24
- package/dist/renderables/spinner.js +101 -49
- package/dist/renderables/status.js +1 -1
- package/dist/renderables/syntax.d.ts +10 -9
- package/dist/renderables/syntax.js +5 -3
- package/dist/renderables/table.d.ts +48 -46
- package/dist/renderables/table.js +170 -208
- package/dist/renderables/traceback.d.ts +3 -3
- package/dist/renderables/traceback.js +12 -10
- package/dist/renderables/tree.d.ts +28 -25
- package/dist/renderables/tree.js +108 -58
- package/dist/renderables/viewport.d.ts +77 -38
- package/dist/renderables/viewport.js +156 -72
- 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 +7 -6
- package/dist/themes/ramp.d.ts +62 -4
- package/dist/themes/ramp.js +82 -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 +23 -14
- package/dist/widgets/focus-manager.js +34 -33
- package/dist/widgets/hit.d.ts +6 -0
- package/dist/widgets/hit.js +8 -1
- package/dist/widgets/index.d.ts +1 -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 +1 -1
- package/dist/widgets/static-item.js +6 -7
- package/dist/widgets/text-input.d.ts +53 -47
- package/dist/widgets/text-input.js +456 -399
- 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 +32 -6
- package/dist/widgets/widget-app.js +74 -27
- package/dist/widgets/widget-base.d.ts +2 -2
- package/dist/widgets/widget-base.js +29 -21
- package/package.json +9 -5
package/dist/core/cells.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Terminal cell width calculation.
|
|
3
|
-
* Handles ASCII, CJK (double-width), and emoji characters.
|
|
2
|
+
* Terminal cell width calculation: Rich's `cell_len`, ported.
|
|
4
3
|
*/
|
|
5
4
|
/**
|
|
6
5
|
* Returns the terminal cell width of a string.
|
|
@@ -72,13 +71,20 @@ export declare function setCellSize(text: string, totalWidth: CellCol): string;
|
|
|
72
71
|
*/
|
|
73
72
|
export declare function splitText(text: string, position: CellCol): [string, string];
|
|
74
73
|
/**
|
|
75
|
-
* Cuts `text`
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
74
|
+
* Cuts `text` at each of `positions`, as the reference's `Segment.split_cells`
|
|
75
|
+
* cuts at one: a wide glyph a cut goes through fits neither side, so it leaves
|
|
76
|
+
* a space in each of its cells, where `splitText` keeps it whole on the right.
|
|
77
|
+
* Every other character lands in exactly one piece, so each piece is exactly
|
|
78
|
+
* as wide as the distance between its cuts. `positions` are non-decreasing and
|
|
79
|
+
* fall inside `text`: from 0 to one short of its width.
|
|
80
|
+
*
|
|
81
|
+
* One walk over `text` however many positions there are, ending at the last
|
|
82
|
+
* of them: cutting what is left again at every position re-measures and copies
|
|
83
|
+
* the rest each time, which is quadratic in a long enough string, and walking
|
|
84
|
+
* past the last position makes one cut near the start of a long string pay for
|
|
85
|
+
* all of it.
|
|
80
86
|
*/
|
|
81
|
-
export declare function cutCells(text: string,
|
|
87
|
+
export declare function cutCells(text: string, positions: readonly CellCol[]): string[];
|
|
82
88
|
/**
|
|
83
89
|
* Wraps text into lines of at most `maxWidth` cells, preserving every code
|
|
84
90
|
* point: `chopCells(t, w).join("") === t`. Unlike `splitText` this never pads,
|
|
@@ -90,6 +96,22 @@ export declare function cutCells(text: string, position: CellCol): [string, stri
|
|
|
90
96
|
* or less is no budget at all, so the text comes back as one line.
|
|
91
97
|
*/
|
|
92
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
|
+
}>;
|
|
93
115
|
/**
|
|
94
116
|
* `line` split at each of `cuts`, in one walk over it. Converting each cut to a
|
|
95
117
|
* code-unit offset on its own rescans the line from the start every time, which
|
|
@@ -97,13 +119,20 @@ export declare function chopCells(text: string, maxWidth: CellCol): string[];
|
|
|
97
119
|
*/
|
|
98
120
|
export declare function splitAtCells(line: string, cuts: readonly CellCol[]): string[];
|
|
99
121
|
/**
|
|
100
|
-
* The grapheme clusters of `text`, in order: the unit `
|
|
101
|
-
*
|
|
102
|
-
*
|
|
122
|
+
* The grapheme clusters of `text`, in order: the unit `cellLen` of a whole
|
|
123
|
+
* string is the sum of. Summing `cellLen` over code points instead disagrees
|
|
124
|
+
* with it on every glyph built from several:
|
|
103
125
|
* `❤️` is 1 + 0 by code point and 2 as a cluster, `👨👩👧` is 6 and 2, and a cut
|
|
104
126
|
* between two of its code points leaves half a glyph.
|
|
105
127
|
*/
|
|
106
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>;
|
|
107
136
|
/**
|
|
108
137
|
* Returns the largest prefix of `text` whose cell width fits within `cap` cells,
|
|
109
138
|
* cut between grapheme clusters. No padding — the returned string may be
|
|
@@ -126,19 +155,6 @@ export declare function cellFitEnd(text: string, cap: CellCol): string;
|
|
|
126
155
|
* grapheme clusters, and every cluster boundary is a code-point boundary.
|
|
127
156
|
*/
|
|
128
157
|
export declare function cellFitFrom(text: string, startCU: CodePoint, cap: CellCol): CodePoint;
|
|
129
|
-
/**
|
|
130
|
-
* Like `cellFitFrom`, but for any `startCU` inside `text` the result is
|
|
131
|
-
* strictly past it: a glyph too wide for `cap` is force-taken whole and
|
|
132
|
-
* overflows `cap` by its own width. So a `while (i < text.length)` loop driven
|
|
133
|
-
* by this terminates by construction, and the re-brand is honest — both
|
|
134
|
-
* operands land on code-point boundaries, so their max does too.
|
|
135
|
-
*
|
|
136
|
-
* [LAW:single-enforcer] `cellFit` documents that its caller must choose between
|
|
137
|
-
* force-taking and skipping; this is that choice, made once. It was made twice
|
|
138
|
-
* before and the copies drifted — the textarea's soft wrap force-took,
|
|
139
|
-
* `chopCells` did neither and hung.
|
|
140
|
-
*/
|
|
141
|
-
export declare function cellStepFrom(text: string, startCU: CodePoint, cap: CellCol): CodePoint;
|
|
142
158
|
/**
|
|
143
159
|
* The code-unit offset a visual column falls at: the end of the largest
|
|
144
160
|
* prefix of `content` within `cellCol` cells, so a column inside a wide glyph
|
package/dist/core/cells.js
CHANGED
|
@@ -1,11 +1,62 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Terminal cell width calculation.
|
|
3
|
-
* Handles ASCII, CJK (double-width), and emoji characters.
|
|
2
|
+
* Terminal cell width calculation: Rich's `cell_len`, ported.
|
|
4
3
|
*/
|
|
5
|
-
import
|
|
4
|
+
import { codePointWidth, widensToEmoji } from "./cell-widths.js";
|
|
6
5
|
import { Memo } from "./memo.js";
|
|
7
|
-
|
|
6
|
+
import { pattern } from "./pattern.js";
|
|
8
7
|
const cellLenMemo = new Memo();
|
|
8
|
+
const ZWJ = 0x200d;
|
|
9
|
+
const VS16 = 0xfe0f;
|
|
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" });
|
|
16
|
+
// Rich's `cell_len` over a run of text: each code point the cells Rich's table
|
|
17
|
+
// gives it, but that a ZWJ takes the code point after it into the glyph before
|
|
18
|
+
// so neither adds a cell, and VS16 widens the last code point measured to two
|
|
19
|
+
// cells when the table lists it as one that emoji presentation widens.
|
|
20
|
+
function richCells(text) {
|
|
21
|
+
let cells = 0;
|
|
22
|
+
let widenable = false;
|
|
23
|
+
let joined = false;
|
|
24
|
+
for (const ch of text) {
|
|
25
|
+
const codePoint = ch.codePointAt(0);
|
|
26
|
+
if (joined) {
|
|
27
|
+
joined = false;
|
|
28
|
+
}
|
|
29
|
+
else if (codePoint === ZWJ) {
|
|
30
|
+
joined = true;
|
|
31
|
+
}
|
|
32
|
+
else if (codePoint === VS16) {
|
|
33
|
+
cells += widenable ? 1 : 0;
|
|
34
|
+
widenable = false;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
const width = codePointWidth(codePoint);
|
|
38
|
+
cells += width;
|
|
39
|
+
widenable = width > 0 ? widensToEmoji(codePoint) : widenable;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return cells;
|
|
43
|
+
}
|
|
44
|
+
// [LAW:one-source-of-truth] the single authority for cell width. A string is
|
|
45
|
+
// the sum of its grapheme clusters, each measured as Rich measures it, so a cut
|
|
46
|
+
// between clusters splits the width exactly; the two part only where a ZWJ or
|
|
47
|
+
// VS16 reaches across a cluster boundary, as in `a` ZWJ `b`, which Rich counts
|
|
48
|
+
// as one cell and two clusters count as two. Without either, every code point
|
|
49
|
+
// counts alone and no segmentation is needed.
|
|
50
|
+
function measure(text) {
|
|
51
|
+
if (PRINTABLE_ASCII.test(text))
|
|
52
|
+
return text.length;
|
|
53
|
+
if (!JOINERS.test(text))
|
|
54
|
+
return richCells(text);
|
|
55
|
+
let cells = 0;
|
|
56
|
+
for (const { segment } of SEGMENTER.segment(text))
|
|
57
|
+
cells += richCells(segment);
|
|
58
|
+
return cells;
|
|
59
|
+
}
|
|
9
60
|
/**
|
|
10
61
|
* Returns the terminal cell width of a string.
|
|
11
62
|
*/
|
|
@@ -13,7 +64,7 @@ export function cellLen(text) {
|
|
|
13
64
|
if (text.length === 0)
|
|
14
65
|
return 0;
|
|
15
66
|
// Short strings recur (words, cells, labels); a long one is rarely measured twice.
|
|
16
|
-
return text.length <= 64 ? cellLenMemo.get(text,
|
|
67
|
+
return text.length <= 64 ? cellLenMemo.get(text, measure) : measure(text);
|
|
17
68
|
}
|
|
18
69
|
/** Brand a raw number as a CellCol. Use only at trust boundaries. */
|
|
19
70
|
export function asCellCol(n) { return n; }
|
|
@@ -82,22 +133,49 @@ export function splitText(text, position) {
|
|
|
82
133
|
return [left + " ".repeat(position - cellLen(left)), text.slice(left.length)];
|
|
83
134
|
}
|
|
84
135
|
/**
|
|
85
|
-
* Cuts `text`
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
136
|
+
* Cuts `text` at each of `positions`, as the reference's `Segment.split_cells`
|
|
137
|
+
* cuts at one: a wide glyph a cut goes through fits neither side, so it leaves
|
|
138
|
+
* a space in each of its cells, where `splitText` keeps it whole on the right.
|
|
139
|
+
* Every other character lands in exactly one piece, so each piece is exactly
|
|
140
|
+
* as wide as the distance between its cuts. `positions` are non-decreasing and
|
|
141
|
+
* fall inside `text`: from 0 to one short of its width.
|
|
142
|
+
*
|
|
143
|
+
* One walk over `text` however many positions there are, ending at the last
|
|
144
|
+
* of them: cutting what is left again at every position re-measures and copies
|
|
145
|
+
* the rest each time, which is quadratic in a long enough string, and walking
|
|
146
|
+
* past the last position makes one cut near the start of a long string pay for
|
|
147
|
+
* all of it.
|
|
90
148
|
*/
|
|
91
|
-
export function cutCells(text,
|
|
92
|
-
const
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
149
|
+
export function cutCells(text, positions) {
|
|
150
|
+
const pieces = [];
|
|
151
|
+
// The current piece is `lead` blank cells, then `text` from `start` to `at`.
|
|
152
|
+
let start = 0;
|
|
153
|
+
let lead = 0;
|
|
154
|
+
let at = 0;
|
|
155
|
+
let col = 0;
|
|
156
|
+
let next = 0;
|
|
157
|
+
for (const glyph of clustersFrom(text, asCodePoint(0))) {
|
|
158
|
+
if (next === positions.length)
|
|
159
|
+
break;
|
|
160
|
+
const end = col + cellLen(glyph);
|
|
161
|
+
// The first cell of this glyph no piece has taken yet.
|
|
162
|
+
let from = col;
|
|
163
|
+
for (; next < positions.length && positions[next] < end; next++) {
|
|
164
|
+
const cut = positions[next];
|
|
165
|
+
pieces.push(" ".repeat(lead) + text.slice(start, at) + " ".repeat(cut - from));
|
|
166
|
+
lead = 0;
|
|
167
|
+
// A cut between glyphs leaves this one to the next piece; a cut through
|
|
168
|
+
// it blanks it, and the pieces after the cut start past it.
|
|
169
|
+
start = cut === col ? at : at + glyph.length;
|
|
170
|
+
from = cut;
|
|
171
|
+
}
|
|
172
|
+
if (from > col)
|
|
173
|
+
lead = end - from;
|
|
174
|
+
at += glyph.length;
|
|
175
|
+
col = end;
|
|
176
|
+
}
|
|
177
|
+
pieces.push(" ".repeat(lead) + text.slice(start));
|
|
178
|
+
return pieces;
|
|
101
179
|
}
|
|
102
180
|
/**
|
|
103
181
|
* Wraps text into lines of at most `maxWidth` cells, preserving every code
|
|
@@ -112,14 +190,38 @@ export function cutCells(text, position) {
|
|
|
112
190
|
export function chopCells(text, maxWidth) {
|
|
113
191
|
if (maxWidth <= 0 || text.length === 0)
|
|
114
192
|
return [text];
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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;
|
|
121
222
|
}
|
|
122
|
-
|
|
223
|
+
if (at > start)
|
|
224
|
+
yield { start: asCodePoint(start), end: asCodePoint(at) };
|
|
123
225
|
}
|
|
124
226
|
/**
|
|
125
227
|
* `line` split at each of `cuts`, in one walk over it. Converting each cut to a
|
|
@@ -141,41 +243,41 @@ export function splitAtCells(line, cuts) {
|
|
|
141
243
|
}
|
|
142
244
|
return [...pieces, line.slice(start)];
|
|
143
245
|
}
|
|
144
|
-
// [LAW:no-shared-mutable-globals] Private memos, written only by `segmentsOf`:
|
|
145
|
-
// the segmenter, and the segmentation of the last string walked, so a caller
|
|
146
|
-
// stepping along one string does not re-segment the whole of it every step.
|
|
147
|
-
let segmenter;
|
|
148
|
-
let lastSegmented;
|
|
149
|
-
/** The grapheme segmentation of `text`, reusing the last one when it is the same string. */
|
|
150
|
-
function segmentsOf(text) {
|
|
151
|
-
if (lastSegmented?.text !== text) {
|
|
152
|
-
segmenter ??= new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
153
|
-
lastSegmented = { text, segments: segmenter.segment(text) };
|
|
154
|
-
}
|
|
155
|
-
return lastSegmented.segments;
|
|
156
|
-
}
|
|
157
246
|
/**
|
|
158
|
-
* The grapheme clusters of `text`, in order: the unit `
|
|
159
|
-
*
|
|
160
|
-
*
|
|
247
|
+
* The grapheme clusters of `text`, in order: the unit `cellLen` of a whole
|
|
248
|
+
* string is the sum of. Summing `cellLen` over code points instead disagrees
|
|
249
|
+
* with it on every glyph built from several:
|
|
161
250
|
* `❤️` is 1 + 0 by code point and 2 as a cluster, `👨👩👧` is 6 and 2, and a cut
|
|
162
251
|
* between two of its code points leaves half a glyph.
|
|
163
252
|
*/
|
|
164
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("");
|
|
165
258
|
return Array.from(clustersFrom(text, asCodePoint(0)));
|
|
166
259
|
}
|
|
167
260
|
/**
|
|
168
261
|
* The clusters of `text` from code unit `start` on, lazily, so a walk that
|
|
169
262
|
* stops early pays only for the clusters it reads.
|
|
170
263
|
*/
|
|
171
|
-
function* clustersFrom(text, start) {
|
|
172
|
-
const segments =
|
|
264
|
+
export function* clustersFrom(text, start) {
|
|
265
|
+
const segments = SEGMENTER.segment(text);
|
|
173
266
|
for (let at = start, s = segments.containing(at); s; s = segments.containing(at)) {
|
|
174
267
|
const end = s.index + s.segment.length;
|
|
175
268
|
yield text.slice(at, end);
|
|
176
269
|
at = asCodePoint(end);
|
|
177
270
|
}
|
|
178
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
|
+
}
|
|
179
281
|
/**
|
|
180
282
|
* Returns the largest prefix of `text` whose cell width fits within `cap` cells,
|
|
181
283
|
* cut between grapheme clusters. No padding — the returned string may be
|
|
@@ -190,7 +292,7 @@ export function cellFit(text, cap) {
|
|
|
190
292
|
}
|
|
191
293
|
/** `cellFit` from the other end: the largest suffix of `text` within `cap` cells. */
|
|
192
294
|
export function cellFitEnd(text, cap) {
|
|
193
|
-
return text.slice(text.length - fitLength(
|
|
295
|
+
return text.slice(text.length - fitLength(clustersBefore(text, asCodePoint(text.length)), cap));
|
|
194
296
|
}
|
|
195
297
|
/** The code units of the leading `clusters` that fit within `cap` cells. */
|
|
196
298
|
function fitLength(clusters, cap) {
|
|
@@ -217,22 +319,6 @@ function fitLength(clusters, cap) {
|
|
|
217
319
|
export function cellFitFrom(text, startCU, cap) {
|
|
218
320
|
return asCodePoint(startCU + fitLength(clustersFrom(text, startCU), cap));
|
|
219
321
|
}
|
|
220
|
-
/**
|
|
221
|
-
* Like `cellFitFrom`, but for any `startCU` inside `text` the result is
|
|
222
|
-
* strictly past it: a glyph too wide for `cap` is force-taken whole and
|
|
223
|
-
* overflows `cap` by its own width. So a `while (i < text.length)` loop driven
|
|
224
|
-
* by this terminates by construction, and the re-brand is honest — both
|
|
225
|
-
* operands land on code-point boundaries, so their max does too.
|
|
226
|
-
*
|
|
227
|
-
* [LAW:single-enforcer] `cellFit` documents that its caller must choose between
|
|
228
|
-
* force-taking and skipping; this is that choice, made once. It was made twice
|
|
229
|
-
* before and the copies drifted — the textarea's soft wrap force-took,
|
|
230
|
-
* `chopCells` did neither and hung.
|
|
231
|
-
*/
|
|
232
|
-
export function cellStepFrom(text, startCU, cap) {
|
|
233
|
-
const [first = ""] = clustersFrom(text, startCU);
|
|
234
|
-
return asCodePoint(Math.max(cellFitFrom(text, startCU, cap), startCU + first.length));
|
|
235
|
-
}
|
|
236
322
|
/**
|
|
237
323
|
* The code-unit offset a visual column falls at: the end of the largest
|
|
238
324
|
* prefix of `content` within `cellCol` cells, so a column inside a wide glyph
|
|
@@ -253,9 +339,8 @@ export function nextGrapheme(s, cu) {
|
|
|
253
339
|
}
|
|
254
340
|
/** `nextGrapheme` backwards: the start of the cluster ending at `cu`; 0 at the start. */
|
|
255
341
|
export function prevGrapheme(s, cu) {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
return asCodePoint(segmentsOf(s).containing(cu - 1).index);
|
|
342
|
+
const [cluster = ""] = clustersBefore(s, cu);
|
|
343
|
+
return asCodePoint(cu - cluster.length);
|
|
259
344
|
}
|
|
260
345
|
/**
|
|
261
346
|
* Where an offset into `s` comes to rest as a cursor: itself when it is a
|
|
@@ -265,7 +350,7 @@ export function prevGrapheme(s, cu) {
|
|
|
265
350
|
*/
|
|
266
351
|
export function graphemeBoundary(s, cu) {
|
|
267
352
|
const at = Math.min(cu, s.length);
|
|
268
|
-
const cluster =
|
|
353
|
+
const cluster = SEGMENTER.segment(s).containing(at);
|
|
269
354
|
return asCodePoint(cluster === undefined || cluster.index === at ? at : cluster.index + cluster.segment.length);
|
|
270
355
|
}
|
|
271
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()));
|