@promptctl/rich-js 0.18.0 → 0.18.1
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/dist/core/anchor.d.ts +45 -0
- package/dist/core/anchor.js +39 -0
- package/dist/core/ansi.d.ts +65 -0
- package/dist/core/ansi.js +231 -0
- package/dist/core/box.d.ts +143 -0
- package/dist/core/box.js +379 -0
- package/dist/core/cells.d.ts +159 -0
- package/dist/core/cells.js +287 -0
- package/dist/core/color.d.ts +246 -0
- package/dist/core/color.js +1038 -0
- package/dist/core/console.d.ts +165 -0
- package/dist/core/console.js +527 -0
- package/dist/core/emoji.d.ts +24 -0
- package/dist/core/emoji.js +391 -0
- package/dist/core/export-html.d.ts +59 -0
- package/dist/core/export-html.js +200 -0
- package/dist/core/export-lines.d.ts +121 -0
- package/dist/core/export-lines.js +141 -0
- package/dist/core/highlighter.d.ts +33 -0
- package/dist/core/highlighter.js +124 -0
- package/dist/core/json.d.ts +27 -0
- package/dist/core/json.js +53 -0
- package/dist/core/markup.d.ts +119 -0
- package/dist/core/markup.js +731 -0
- package/dist/core/measure.d.ts +27 -0
- package/dist/core/measure.js +62 -0
- package/dist/core/oklch.d.ts +161 -0
- package/dist/core/oklch.js +312 -0
- package/dist/core/osc8.d.ts +68 -0
- package/dist/core/osc8.js +90 -0
- package/dist/core/place.d.ts +30 -0
- package/dist/core/place.js +51 -0
- package/dist/core/pretty.d.ts +144 -0
- package/dist/core/pretty.js +476 -0
- package/dist/core/protocol.d.ts +267 -0
- package/dist/core/protocol.js +163 -0
- package/dist/core/render.d.ts +84 -0
- package/dist/core/render.js +145 -0
- package/dist/core/segment.d.ts +161 -0
- package/dist/core/segment.js +492 -0
- package/dist/core/spinnerData.d.ts +12 -0
- package/dist/core/spinnerData.js +117 -0
- package/dist/core/strip.d.ts +141 -0
- package/dist/core/strip.js +305 -0
- package/dist/core/style.d.ts +169 -0
- package/dist/core/style.js +695 -0
- package/dist/core/subscription.d.ts +12 -0
- package/dist/core/subscription.js +12 -0
- package/dist/core/text.d.ts +256 -0
- package/dist/core/text.js +976 -0
- package/dist/core/wrap.d.ts +41 -0
- package/dist/core/wrap.js +127 -0
- package/dist/host/app.d.ts +121 -0
- package/dist/host/app.js +255 -0
- package/dist/host/host-environment.d.ts +26 -0
- package/dist/host/host-environment.js +44 -0
- package/dist/host/index.d.ts +5 -0
- package/dist/host/index.js +3 -0
- package/dist/host/terminal-host.d.ts +179 -0
- package/dist/host/terminal-host.js +186 -0
- package/dist/index.d.ts +91 -0
- package/dist/index.js +107 -0
- package/dist/node/prompt.d.ts +21 -0
- package/dist/node/prompt.js +30 -0
- package/dist/node/save.d.ts +17 -0
- package/dist/node/save.js +21 -0
- package/dist/node/terminal-host.d.ts +113 -0
- package/dist/node/terminal-host.js +227 -0
- package/dist/node/traceback.d.ts +37 -0
- package/dist/node/traceback.js +134 -0
- package/dist/renderables/align.d.ts +16 -0
- package/dist/renderables/align.js +33 -0
- package/dist/renderables/columns.d.ts +61 -0
- package/dist/renderables/columns.js +152 -0
- package/dist/renderables/constrain.d.ts +15 -0
- package/dist/renderables/constrain.js +37 -0
- package/dist/renderables/embed.d.ts +50 -0
- package/dist/renderables/embed.js +71 -0
- package/dist/renderables/flexStrip.d.ts +41 -0
- package/dist/renderables/flexStrip.js +200 -0
- package/dist/renderables/group.d.ts +11 -0
- package/dist/renderables/group.js +17 -0
- package/dist/renderables/layout.d.ts +92 -0
- package/dist/renderables/layout.js +312 -0
- package/dist/renderables/live.d.ts +50 -0
- package/dist/renderables/live.js +175 -0
- package/dist/renderables/markdown.d.ts +24 -0
- package/dist/renderables/markdown.js +184 -0
- package/dist/renderables/padding.d.ts +38 -0
- package/dist/renderables/padding.js +141 -0
- package/dist/renderables/panel.d.ts +116 -0
- package/dist/renderables/panel.js +307 -0
- package/dist/renderables/progress.d.ts +90 -0
- package/dist/renderables/progress.js +238 -0
- package/dist/renderables/progressBar.d.ts +31 -0
- package/dist/renderables/progressBar.js +53 -0
- package/dist/renderables/prompt.d.ts +49 -0
- package/dist/renderables/prompt.js +125 -0
- package/dist/renderables/rule.d.ts +25 -0
- package/dist/renderables/rule.js +90 -0
- package/dist/renderables/spinner.d.ts +31 -0
- package/dist/renderables/spinner.js +70 -0
- package/dist/renderables/status.d.ts +23 -0
- package/dist/renderables/status.js +60 -0
- package/dist/renderables/syntax.d.ts +35 -0
- package/dist/renderables/syntax.js +115 -0
- package/dist/renderables/table.d.ts +194 -0
- package/dist/renderables/table.js +795 -0
- package/dist/renderables/traceback.d.ts +31 -0
- package/dist/renderables/traceback.js +108 -0
- package/dist/renderables/tree.d.ts +68 -0
- package/dist/renderables/tree.js +150 -0
- package/dist/renderables/viewport.d.ts +114 -0
- package/dist/renderables/viewport.js +198 -0
- package/dist/template-bindings/color-funcs.d.ts +87 -0
- package/dist/template-bindings/color-funcs.js +217 -0
- package/dist/template-bindings/helpers.d.ts +24 -0
- package/dist/template-bindings/helpers.js +48 -0
- package/dist/template-bindings/index.d.ts +107 -0
- package/dist/template-bindings/index.js +146 -0
- package/dist/template-bindings/palette-funcs.d.ts +94 -0
- package/dist/template-bindings/palette-funcs.js +141 -0
- package/dist/template-bindings/style-funcs.d.ts +28 -0
- package/dist/template-bindings/style-funcs.js +155 -0
- package/dist/themes/buildPalette.d.ts +36 -0
- package/dist/themes/buildPalette.js +56 -0
- package/dist/themes/colorMath.d.ts +82 -0
- package/dist/themes/colorMath.js +254 -0
- package/dist/themes/colorRef.d.ts +51 -0
- package/dist/themes/colorRef.js +96 -0
- package/dist/themes/data/ansi-tables.d.ts +14 -0
- package/dist/themes/data/ansi-tables.js +49 -0
- package/dist/themes/data/atom-one-dark.d.ts +3 -0
- package/dist/themes/data/atom-one-dark.js +177 -0
- package/dist/themes/data/atom-one-light.d.ts +3 -0
- package/dist/themes/data/atom-one-light.js +177 -0
- package/dist/themes/data/catppuccin-frappe.d.ts +3 -0
- package/dist/themes/data/catppuccin-frappe.js +177 -0
- package/dist/themes/data/catppuccin-latte.d.ts +3 -0
- package/dist/themes/data/catppuccin-latte.js +177 -0
- package/dist/themes/data/catppuccin-macchiato.d.ts +3 -0
- package/dist/themes/data/catppuccin-macchiato.js +177 -0
- package/dist/themes/data/catppuccin-mocha.d.ts +3 -0
- package/dist/themes/data/catppuccin-mocha.js +177 -0
- package/dist/themes/data/cyberpunk.d.ts +17 -0
- package/dist/themes/data/cyberpunk.js +193 -0
- package/dist/themes/data/default.d.ts +3 -0
- package/dist/themes/data/default.js +160 -0
- package/dist/themes/data/dracula.d.ts +3 -0
- package/dist/themes/data/dracula.js +177 -0
- package/dist/themes/data/flexoki.d.ts +3 -0
- package/dist/themes/data/flexoki.js +177 -0
- package/dist/themes/data/gruvbox.d.ts +3 -0
- package/dist/themes/data/gruvbox.js +177 -0
- package/dist/themes/data/index.d.ts +35 -0
- package/dist/themes/data/index.js +64 -0
- package/dist/themes/data/monokai.d.ts +3 -0
- package/dist/themes/data/monokai.js +177 -0
- package/dist/themes/data/nord.d.ts +3 -0
- package/dist/themes/data/nord.js +177 -0
- package/dist/themes/data/rose-pine-dawn.d.ts +3 -0
- package/dist/themes/data/rose-pine-dawn.js +177 -0
- package/dist/themes/data/rose-pine-moon.d.ts +3 -0
- package/dist/themes/data/rose-pine-moon.js +177 -0
- package/dist/themes/data/rose-pine.d.ts +3 -0
- package/dist/themes/data/rose-pine.js +177 -0
- package/dist/themes/data/solarized-dark.d.ts +3 -0
- package/dist/themes/data/solarized-dark.js +177 -0
- package/dist/themes/data/solarized-light.d.ts +3 -0
- package/dist/themes/data/solarized-light.js +177 -0
- package/dist/themes/data/svg-export.d.ts +3 -0
- package/dist/themes/data/svg-export.js +177 -0
- package/dist/themes/data/textual-ansi.d.ts +3 -0
- package/dist/themes/data/textual-ansi.js +160 -0
- package/dist/themes/data/textual-dark.d.ts +3 -0
- package/dist/themes/data/textual-dark.js +160 -0
- package/dist/themes/data/textual-light.d.ts +3 -0
- package/dist/themes/data/textual-light.js +160 -0
- package/dist/themes/data/tokyo-night.d.ts +3 -0
- package/dist/themes/data/tokyo-night.js +177 -0
- package/dist/themes/data/types.d.ts +31 -0
- package/dist/themes/data/types.js +10 -0
- package/dist/themes/palette.d.ts +21 -0
- package/dist/themes/palette.js +28 -0
- package/dist/themes/ramp.d.ts +83 -0
- package/dist/themes/ramp.js +122 -0
- package/dist/themes/registry.d.ts +44 -0
- package/dist/themes/registry.js +94 -0
- package/dist/themes/terminalThemes.d.ts +49 -0
- package/dist/themes/terminalThemes.js +63 -0
- package/dist/themes/transpose.d.ts +87 -0
- package/dist/themes/transpose.js +138 -0
- package/dist/widgets/button.d.ts +49 -0
- package/dist/widgets/button.js +208 -0
- package/dist/widgets/checkbox.d.ts +41 -0
- package/dist/widgets/checkbox.js +157 -0
- package/dist/widgets/dropdown.d.ts +67 -0
- package/dist/widgets/dropdown.js +450 -0
- package/dist/widgets/event-router.d.ts +71 -0
- package/dist/widgets/event-router.js +629 -0
- package/dist/widgets/focus-manager.d.ts +45 -0
- package/dist/widgets/focus-manager.js +191 -0
- package/dist/widgets/hit.d.ts +64 -0
- package/dist/widgets/hit.js +113 -0
- package/dist/widgets/index.d.ts +20 -0
- package/dist/widgets/index.js +14 -0
- package/dist/widgets/slider.d.ts +57 -0
- package/dist/widgets/slider.js +270 -0
- package/dist/widgets/static-item.d.ts +34 -0
- package/dist/widgets/static-item.js +54 -0
- package/dist/widgets/text-input.d.ts +274 -0
- package/dist/widgets/text-input.js +1165 -0
- package/dist/widgets/toggle.d.ts +44 -0
- package/dist/widgets/toggle.js +180 -0
- package/dist/widgets/types.d.ts +87 -0
- package/dist/widgets/types.js +30 -0
- package/dist/widgets/widget-app.d.ts +63 -0
- package/dist/widgets/widget-app.js +199 -0
- package/dist/widgets/widget-base.d.ts +40 -0
- package/dist/widgets/widget-base.js +165 -0
- package/package.json +6 -2
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal cell width calculation.
|
|
3
|
+
* Handles ASCII, CJK (double-width), and emoji characters.
|
|
4
|
+
*/
|
|
5
|
+
import stringWidth from "string-width";
|
|
6
|
+
// [LAW:one-source-of-truth] string-width is the single authority for cell width
|
|
7
|
+
const cellLenCache = new Map();
|
|
8
|
+
const CACHE_MAX = 4096;
|
|
9
|
+
/**
|
|
10
|
+
* Returns the terminal cell width of a string.
|
|
11
|
+
*/
|
|
12
|
+
export function cellLen(text) {
|
|
13
|
+
if (text.length === 0)
|
|
14
|
+
return 0;
|
|
15
|
+
// Fast path for pure ASCII
|
|
16
|
+
if (text.length <= 64) {
|
|
17
|
+
const cached = cellLenCache.get(text);
|
|
18
|
+
if (cached !== undefined)
|
|
19
|
+
return cached;
|
|
20
|
+
}
|
|
21
|
+
const width = stringWidth(text);
|
|
22
|
+
if (text.length <= 64) {
|
|
23
|
+
if (cellLenCache.size >= CACHE_MAX)
|
|
24
|
+
cellLenCache.clear();
|
|
25
|
+
cellLenCache.set(text, width);
|
|
26
|
+
}
|
|
27
|
+
return width;
|
|
28
|
+
}
|
|
29
|
+
/** Brand a raw number as a CellCol. Use only at trust boundaries. */
|
|
30
|
+
export function asCellCol(n) { return n; }
|
|
31
|
+
/** Brand a raw number as a CodeUnit. Use only at trust boundaries. */
|
|
32
|
+
export function asCodeUnit(n) { return n; }
|
|
33
|
+
/** Brand a raw number as a CodePoint. Use only when the value is known to be
|
|
34
|
+
* on a Unicode code-point boundary. */
|
|
35
|
+
export function asCodePoint(n) { return n; }
|
|
36
|
+
/**
|
|
37
|
+
* [LAW:parse-dont-validate] A caller's number as a count of cells — the checked
|
|
38
|
+
* counterpart to `asCellCol`'s unchecked brand, and the one place the rule "a
|
|
39
|
+
* cell count is a non-negative integer" is written.
|
|
40
|
+
*
|
|
41
|
+
* [LAW:one-source-of-truth] Every renderable is handed `RenderOptions.maxWidth`
|
|
42
|
+
* as a plain `number`, so every renderable used to answer this for itself and
|
|
43
|
+
* they disagreed: at NaN, `Table` collapsed to one cell of garbage, `Columns`
|
|
44
|
+
* and `Layout` threw `Invalid array length`, and `RichText` ignored the request
|
|
45
|
+
* and emitted its full natural width. Parse at the entry to `render`/`measure`
|
|
46
|
+
* and nothing downstream re-asks.
|
|
47
|
+
*
|
|
48
|
+
* The comparison is the point, not `Math.max(0, n)`: NaN fails every comparison
|
|
49
|
+
* and so floors here, where `Math.max` would return it and let it poison every
|
|
50
|
+
* bounds check downstream. Integral because a cell is not divisible — a
|
|
51
|
+
* fractional budget reaches a largest-remainder division, which grants a whole
|
|
52
|
+
* cell against a fractional residue and hands out more width than it was given.
|
|
53
|
+
*
|
|
54
|
+
* `Infinity` is outside what the comparison floors, so it passes through
|
|
55
|
+
* unchanged, and no clamp belongs here: the finite answer to an unbounded offer
|
|
56
|
+
* is the renderable's own natural width, which this function cannot see from
|
|
57
|
+
* the number alone. `withBoundedWidth` in `protocol.ts` is the second half of
|
|
58
|
+
* the parse and the only place that value is resolved.
|
|
59
|
+
*/
|
|
60
|
+
export function cellCount(n) {
|
|
61
|
+
return (n > 0 ? Math.floor(n) : 0);
|
|
62
|
+
}
|
|
63
|
+
// ── Cell-aware string utilities ──────────────────────────────────────────────
|
|
64
|
+
/**
|
|
65
|
+
* Pads or crops a string to exactly `totalWidth` terminal cells.
|
|
66
|
+
* Invariant: cellLen(setCellSize(text, n)) === n (unless n is 0)
|
|
67
|
+
*/
|
|
68
|
+
export function setCellSize(text, totalWidth) {
|
|
69
|
+
if (totalWidth === 0)
|
|
70
|
+
return "";
|
|
71
|
+
const currentWidth = cellLen(text);
|
|
72
|
+
if (currentWidth === totalWidth)
|
|
73
|
+
return text;
|
|
74
|
+
if (currentWidth < totalWidth) {
|
|
75
|
+
return text + " ".repeat(totalWidth - currentWidth);
|
|
76
|
+
}
|
|
77
|
+
return cropToWidth(text, totalWidth);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Splits text at a cell position. Returns [left, right].
|
|
81
|
+
* When the position falls mid-wide-character, the left side is padded
|
|
82
|
+
* to reach exactly `position` cells. The wide char remains in the right side.
|
|
83
|
+
*/
|
|
84
|
+
export function splitText(text, position) {
|
|
85
|
+
if (position <= 0)
|
|
86
|
+
return ["", text];
|
|
87
|
+
const totalWidth = cellLen(text);
|
|
88
|
+
if (position >= totalWidth)
|
|
89
|
+
return [text, ""];
|
|
90
|
+
// Walk characters tracking cell width and source char index
|
|
91
|
+
let width = 0;
|
|
92
|
+
let charIndex = 0;
|
|
93
|
+
for (const char of text) {
|
|
94
|
+
const charWidth = cellLen(char);
|
|
95
|
+
if (width + charWidth > position)
|
|
96
|
+
break;
|
|
97
|
+
width += charWidth;
|
|
98
|
+
charIndex += char.length;
|
|
99
|
+
}
|
|
100
|
+
const left = text.slice(0, charIndex);
|
|
101
|
+
const right = text.slice(charIndex);
|
|
102
|
+
// If we stopped short of position (mid-wide-char), pad left with spaces
|
|
103
|
+
if (width < position) {
|
|
104
|
+
return [left + " ".repeat(position - width), right];
|
|
105
|
+
}
|
|
106
|
+
return [left, right];
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Wraps text into lines of at most `maxWidth` cells, preserving every code
|
|
110
|
+
* point: `chopCells(t, w).join("") === t`. Unlike `splitText` this never pads,
|
|
111
|
+
* so a line ending before a wide glyph is narrower than `maxWidth` rather than
|
|
112
|
+
* padded out to it.
|
|
113
|
+
*
|
|
114
|
+
* A line overflows exactly where the budget cannot be met: a glyph wider than
|
|
115
|
+
* `maxWidth` is force-taken whole rather than dropped, and a `maxWidth` of zero
|
|
116
|
+
* or less is no budget at all, so the text comes back as one line.
|
|
117
|
+
*/
|
|
118
|
+
export function chopCells(text, maxWidth) {
|
|
119
|
+
if (maxWidth <= 0 || text.length === 0)
|
|
120
|
+
return [text];
|
|
121
|
+
const lines = [];
|
|
122
|
+
let start = asCodePoint(0);
|
|
123
|
+
while (start < text.length) {
|
|
124
|
+
const end = cellStepFrom(text, start, maxWidth);
|
|
125
|
+
lines.push(text.slice(start, end));
|
|
126
|
+
start = end;
|
|
127
|
+
}
|
|
128
|
+
return lines;
|
|
129
|
+
}
|
|
130
|
+
// [LAW:no-shared-mutable-globals] A private memo, written only by `graphemes`.
|
|
131
|
+
let segmenter;
|
|
132
|
+
/**
|
|
133
|
+
* The grapheme clusters of `text`, in order: the unit `string-width` measures,
|
|
134
|
+
* and so the unit `cellLen` of a whole string is the sum of. Summing `cellLen`
|
|
135
|
+
* over code points instead disagrees with it on every glyph built from several:
|
|
136
|
+
* `❤️` is 1 + 0 by code point and 2 as a cluster, `👨👩👧` is 6 and 2, and a cut
|
|
137
|
+
* between two of its code points leaves half a glyph.
|
|
138
|
+
*/
|
|
139
|
+
export function graphemes(text) {
|
|
140
|
+
segmenter ??= new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
141
|
+
return Array.from(segmenter.segment(text), (s) => s.segment);
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Returns the largest prefix of `text` whose cell width fits within `cap` cells,
|
|
145
|
+
* cut between grapheme clusters. No padding — the returned string may be
|
|
146
|
+
* narrower than `cap` when the next glyph is wide and would overshoot. Never
|
|
147
|
+
* wider than `cap` cells.
|
|
148
|
+
*
|
|
149
|
+
* When the first glyph already exceeds `cap` cells, returns "" (the caller
|
|
150
|
+
* must decide whether to force-take the glyph or skip it).
|
|
151
|
+
*/
|
|
152
|
+
export function cellFit(text, cap) {
|
|
153
|
+
return fitClusters(graphemes(text), cap).join("");
|
|
154
|
+
}
|
|
155
|
+
/** `cellFit` from the other end: the largest suffix of `text` within `cap` cells. */
|
|
156
|
+
export function cellFitEnd(text, cap) {
|
|
157
|
+
return fitClusters(graphemes(text).reverse(), cap).reverse().join("");
|
|
158
|
+
}
|
|
159
|
+
function fitClusters(clusters, cap) {
|
|
160
|
+
let w = 0;
|
|
161
|
+
let n = 0;
|
|
162
|
+
for (const cluster of clusters) {
|
|
163
|
+
const cw = cellLen(cluster);
|
|
164
|
+
if (w + cw > cap)
|
|
165
|
+
break;
|
|
166
|
+
w += cw;
|
|
167
|
+
n++;
|
|
168
|
+
}
|
|
169
|
+
return clusters.slice(0, n);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Returns the largest code-unit end offset starting from `startCU` whose
|
|
173
|
+
* prefix (from `startCU`) has cell width ≤ `cap`. Iterates from the given
|
|
174
|
+
* offset without slicing the tail, avoiding O(N²) allocation when called
|
|
175
|
+
* repeatedly across a long string.
|
|
176
|
+
*
|
|
177
|
+
* [LAW:types-are-the-program] returns CodePoint because for...of always
|
|
178
|
+
* stops on a code-point boundary.
|
|
179
|
+
*/
|
|
180
|
+
export function cellFitFrom(text, startCU, cap) {
|
|
181
|
+
let w = 0;
|
|
182
|
+
let i = startCU;
|
|
183
|
+
while (i < text.length) {
|
|
184
|
+
const cp = text.codePointAt(i);
|
|
185
|
+
const ch = String.fromCodePoint(cp);
|
|
186
|
+
const cw = cellLen(ch);
|
|
187
|
+
if (w + cw > cap)
|
|
188
|
+
break;
|
|
189
|
+
w += cw;
|
|
190
|
+
i = asCodePoint(i + ch.length);
|
|
191
|
+
}
|
|
192
|
+
return i;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Like `cellFitFrom`, but for any `startCU` inside `text` the result is
|
|
196
|
+
* strictly past it: a glyph too wide for `cap` is force-taken whole and
|
|
197
|
+
* overflows `cap` by its own width. So a `while (i < text.length)` loop driven
|
|
198
|
+
* by this terminates by construction, and the re-brand is honest — both
|
|
199
|
+
* operands land on code-point boundaries, so their max does too.
|
|
200
|
+
*
|
|
201
|
+
* [LAW:single-enforcer] `cellFit` documents that its caller must choose between
|
|
202
|
+
* force-taking and skipping; this is that choice, made once. It was made twice
|
|
203
|
+
* before and the copies drifted — the textarea's soft wrap force-took,
|
|
204
|
+
* `chopCells` did neither and hung.
|
|
205
|
+
*/
|
|
206
|
+
export function cellStepFrom(text, startCU, cap) {
|
|
207
|
+
return asCodePoint(Math.max(cellFitFrom(text, startCU, cap), nextCodePoint(text, startCU)));
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Returns the largest code-unit offset into `content` whose prefix has
|
|
211
|
+
* cell width ≤ `cellCol`. When `cellCol` falls mid-wide-character the
|
|
212
|
+
* function stops before that character (never advances into it).
|
|
213
|
+
* Clamps to `content.length` if `cellCol` exceeds the string's total
|
|
214
|
+
* cell width.
|
|
215
|
+
*
|
|
216
|
+
* This is the inverse of `cellLen(content.slice(0, codeUnit))` — given a
|
|
217
|
+
* visual column, return the corresponding string index.
|
|
218
|
+
*
|
|
219
|
+
* Returns `CodePoint` because `for...of` iteration always stops on a
|
|
220
|
+
* code-point boundary.
|
|
221
|
+
*/
|
|
222
|
+
export function cellColToCodeUnitOffset(content, cellCol) {
|
|
223
|
+
let w = 0;
|
|
224
|
+
let i = 0;
|
|
225
|
+
for (const ch of content) {
|
|
226
|
+
if (w >= cellCol)
|
|
227
|
+
break;
|
|
228
|
+
const cw = cellLen(ch);
|
|
229
|
+
if (w + cw > cellCol)
|
|
230
|
+
break;
|
|
231
|
+
w += cw;
|
|
232
|
+
i += ch.length;
|
|
233
|
+
}
|
|
234
|
+
return asCodePoint(i);
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Advance one full Unicode code point from `cu`, returning the code-unit
|
|
238
|
+
* offset of the start of the *next* code point. Returns `s.length` when
|
|
239
|
+
* already at or past the end.
|
|
240
|
+
*
|
|
241
|
+
* Handles surrogate pairs: when the code point at `cu` is a supplementary
|
|
242
|
+
* character (U+10000…U+10FFFF) it occupies 2 UTF-16 code units, so the
|
|
243
|
+
* returned offset advances by 2.
|
|
244
|
+
*/
|
|
245
|
+
export function nextCodePoint(s, cu) {
|
|
246
|
+
if (cu >= s.length)
|
|
247
|
+
return asCodePoint(s.length);
|
|
248
|
+
const cp = s.codePointAt(cu);
|
|
249
|
+
return asCodePoint(cu + (cp > 0xFFFF ? 2 : 1));
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Step back one full Unicode code point from `cu`, returning the code-unit
|
|
253
|
+
* offset of the start of the *previous* code point. Returns 0 when already
|
|
254
|
+
* at the start.
|
|
255
|
+
*
|
|
256
|
+
* Handles surrogate pairs: when the code unit at `cu - 1` is a low surrogate
|
|
257
|
+
* AND the code unit at `cu - 2` is a high surrogate, steps back 2 code units.
|
|
258
|
+
* Unpaired surrogates are treated as 1-CU characters.
|
|
259
|
+
*/
|
|
260
|
+
export function prevCodePoint(s, cu) {
|
|
261
|
+
if (cu <= 0)
|
|
262
|
+
return asCodePoint(0);
|
|
263
|
+
const low = s.charCodeAt(cu - 1);
|
|
264
|
+
if (low >= 0xDC00 && low <= 0xDFFF && cu >= 2) {
|
|
265
|
+
const high = s.charCodeAt(cu - 2);
|
|
266
|
+
if (high >= 0xD800 && high <= 0xDBFF)
|
|
267
|
+
return asCodePoint(cu - 2);
|
|
268
|
+
}
|
|
269
|
+
return asCodePoint(cu - 1);
|
|
270
|
+
}
|
|
271
|
+
// --- internal ---
|
|
272
|
+
function cropToWidth(text, targetWidth) {
|
|
273
|
+
let width = 0;
|
|
274
|
+
let i = 0;
|
|
275
|
+
// Use the string's code point iterator to handle surrogate pairs
|
|
276
|
+
for (const char of text) {
|
|
277
|
+
const charWidth = cellLen(char);
|
|
278
|
+
if (width + charWidth > targetWidth)
|
|
279
|
+
break;
|
|
280
|
+
width += charWidth;
|
|
281
|
+
i += char.length;
|
|
282
|
+
}
|
|
283
|
+
const cropped = text.slice(0, i);
|
|
284
|
+
// Pad if we couldn't hit the exact width (wide char boundary)
|
|
285
|
+
const diff = targetWidth - width;
|
|
286
|
+
return diff > 0 ? cropped + " ".repeat(diff) : cropped;
|
|
287
|
+
}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal color representation, parsing, downgrading, and ANSI code generation.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Immutable RGBA color value. Alpha defaults to 1 (fully opaque).
|
|
6
|
+
*
|
|
7
|
+
* [LAW:single-enforcer] The constructor is the sole validation site for
|
|
8
|
+
* channel/alpha invariants. Float-arithmetic callers (HSL roundtrips, blends,
|
|
9
|
+
* lerps) are responsible for `Math.round` + clamp before construction; the
|
|
10
|
+
* constructor throws rather than silently masking out-of-range input.
|
|
11
|
+
*/
|
|
12
|
+
export declare class ColorRgba {
|
|
13
|
+
readonly red: number;
|
|
14
|
+
readonly green: number;
|
|
15
|
+
readonly blue: number;
|
|
16
|
+
readonly alpha: number;
|
|
17
|
+
constructor(red: number, green: number, blue: number, alpha?: number);
|
|
18
|
+
/** 6-char `#RRGGBB` when fully opaque, 8-char `#RRGGBBAA` otherwise. */
|
|
19
|
+
get hex(): string;
|
|
20
|
+
/** `rgb(r,g,b)` when fully opaque, `rgba(r,g,b,a)` otherwise. */
|
|
21
|
+
get rgb(): string;
|
|
22
|
+
/** [r/255, g/255, b/255, alpha] — alpha is already 0..1, no division. */
|
|
23
|
+
get normalized(): [number, number, number, number];
|
|
24
|
+
/**
|
|
25
|
+
* Composite this color over an opaque background. Returns a fully opaque
|
|
26
|
+
* ColorRgba (alpha=1). Per-channel linear interpolation by alpha.
|
|
27
|
+
*
|
|
28
|
+
* [LAW:dataflow-not-control-flow] alpha=1 short-circuits to `this`, so
|
|
29
|
+
* callers can invoke this unconditionally; the data decides whether work
|
|
30
|
+
* happens.
|
|
31
|
+
*/
|
|
32
|
+
compositeOver(bg: ColorRgba): ColorRgba;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The surface a terminal draws a translucent colour over. A terminal cannot
|
|
36
|
+
* know what lies under its cells, so the SGR writer (`Style.toSgrCodes`), the
|
|
37
|
+
* strip's seam test and the contrast choosers (`themes/colorMath`) all
|
|
38
|
+
* composite over this one colour. [LAW:one-source-of-truth] One constant, so
|
|
39
|
+
* the colour text is chosen against is the colour the writer draws.
|
|
40
|
+
*/
|
|
41
|
+
export declare const SURFACE_BLACK: ColorRgba;
|
|
42
|
+
/**
|
|
43
|
+
* WCAG 2.x relative luminance (0..1) of an opaque color. The single
|
|
44
|
+
* luminance function in the codebase — `contrastFor`, `contrastRatio`, and
|
|
45
|
+
* any caller that needs to reason about readability all funnel through it.
|
|
46
|
+
* [LAW:one-source-of-truth]
|
|
47
|
+
*/
|
|
48
|
+
export declare function relativeLuminance(c: ColorRgba): number;
|
|
49
|
+
/**
|
|
50
|
+
* WCAG 2.x contrast ratio between two colors, in [1, 21]. Symmetric — the
|
|
51
|
+
* order of arguments does not matter. 4.5 is the AA threshold for normal
|
|
52
|
+
* text, 3.0 for large text.
|
|
53
|
+
*
|
|
54
|
+
* Assumes opaque inputs: alpha is ignored, since the displayed contrast of a
|
|
55
|
+
* translucent color depends on what it composites over. For a translucent
|
|
56
|
+
* foreground, flatten it first (or use `ensureContrast`, which does).
|
|
57
|
+
*/
|
|
58
|
+
export declare function contrastRatio(a: ColorRgba, b: ColorRgba): number;
|
|
59
|
+
export declare class ColorTable {
|
|
60
|
+
private readonly colors;
|
|
61
|
+
private readonly firstIndex;
|
|
62
|
+
private readonly matchCache;
|
|
63
|
+
private readonly readableCache;
|
|
64
|
+
constructor(colors: ColorRgba[], firstIndex?: number);
|
|
65
|
+
get(index: number): ColorRgba;
|
|
66
|
+
private luminanceCache;
|
|
67
|
+
/** Each entry's relative luminance, computed once per table. */
|
|
68
|
+
private luminances;
|
|
69
|
+
/** How many entries the table holds (terminal indices `firstIndex`…). */
|
|
70
|
+
get size(): number;
|
|
71
|
+
/**
|
|
72
|
+
* Finds the nearest table index to the given color (Euclidean RGB distance, cached).
|
|
73
|
+
* Alpha is part of the cache key so two values that differ only in alpha
|
|
74
|
+
* don't collide, but is not used in the distance metric.
|
|
75
|
+
*/
|
|
76
|
+
match(value: ColorRgba): number;
|
|
77
|
+
/**
|
|
78
|
+
* The nearest entry to `value` (the distance `match` uses) among those that
|
|
79
|
+
* clear `minRatio` against `on`; when none does, the entry with the most
|
|
80
|
+
* contrast against `on`. Text downgraded on its own background: `match`
|
|
81
|
+
* moves text and background independently, and two independent roundings
|
|
82
|
+
* can meet in the middle, so a pair that read at 4.5:1 in truecolor can
|
|
83
|
+
* draw at 2:1. [LAW:dataflow-not-control-flow] One scan scores every entry;
|
|
84
|
+
* the ratio decides which one wins.
|
|
85
|
+
*/
|
|
86
|
+
matchReadable(value: ColorRgba, on: ColorRgba, minRatio: number): number;
|
|
87
|
+
/**
|
|
88
|
+
* The nearest entry to `value` (the distance `match` uses) among those
|
|
89
|
+
* `accept` takes, given each entry's colour and terminal index; `undefined`
|
|
90
|
+
* when it takes none. Entries are offered nearest-first (ties to the lower
|
|
91
|
+
* index) and the first taken wins, so an expensive predicate runs only as
|
|
92
|
+
* far out as the answer. Uncached: the predicate is the caller's, and a
|
|
93
|
+
* closure has no key.
|
|
94
|
+
*/
|
|
95
|
+
matchWhere(value: ColorRgba, accept: (entry: ColorRgba, index: number) => boolean): number | undefined;
|
|
96
|
+
}
|
|
97
|
+
export declare enum ColorDepth {
|
|
98
|
+
DEFAULT = 0,
|
|
99
|
+
STANDARD = 1,
|
|
100
|
+
EIGHT_BIT = 2,
|
|
101
|
+
TRUECOLOR = 3,
|
|
102
|
+
WINDOWS = 4
|
|
103
|
+
}
|
|
104
|
+
export interface DetectColorOptions {
|
|
105
|
+
/** Environment to probe. Defaults to `process.env`. */
|
|
106
|
+
env?: NodeJS.ProcessEnv;
|
|
107
|
+
/** Whether output is going to a real terminal. Defaults to `process.stdout?.isTTY`. */
|
|
108
|
+
isTTY?: boolean;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Detect the terminal's color capability from env + TTY state.
|
|
112
|
+
*
|
|
113
|
+
* Probes (in priority order): NO_COLOR (per https://no-color.org —
|
|
114
|
+
* any non-empty value disables color), FORCE_COLOR (numeric/boolean override),
|
|
115
|
+
* TTY presence, TERM=dumb/unknown, COLORTERM, known terminal/TERM_PROGRAM
|
|
116
|
+
* names, and TERM regex patterns. Returns `null` for "no color".
|
|
117
|
+
*
|
|
118
|
+
* [LAW:dataflow-not-control-flow] Same probes run every call; variability is
|
|
119
|
+
* in the env values, not in which checks execute.
|
|
120
|
+
*/
|
|
121
|
+
export declare function detectColorSystem(options?: DetectColorOptions): ColorDepth | null;
|
|
122
|
+
/**
|
|
123
|
+
* Where segments are written, as everything the serializer (`segmentsToString`)
|
|
124
|
+
* needs to know about it: the colour depth SGR is drawn at, and whether OSC 8
|
|
125
|
+
* hyperlinks are emitted. The serializer takes this value whole, so a fact the
|
|
126
|
+
* encoding comes to need joins it here rather than the serializer's signature.
|
|
127
|
+
*/
|
|
128
|
+
export interface Destination {
|
|
129
|
+
readonly colorSystem: ColorDepth | null;
|
|
130
|
+
readonly hyperlinks: boolean;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Resolve a colour spec into a `Destination`.
|
|
134
|
+
*
|
|
135
|
+
* [LAW:single-enforcer] The one place a spec becomes both facts. A hyperlink is
|
|
136
|
+
* not a colour: an explicit depth — `"none"` and `null` included — states a
|
|
137
|
+
* colour choice and keeps links, and under `"auto"` links follow what the
|
|
138
|
+
* terminal can take (no TTY, TERM=dumb: none) while NO_COLOR and FORCE_COLOR=0,
|
|
139
|
+
* colour preferences, leave them alone. A FORCE_COLOR that forces colour on
|
|
140
|
+
* declares the destination takes escapes, so it keeps links on a pipe too.
|
|
141
|
+
*/
|
|
142
|
+
export declare function resolveDestination(spec: string | ColorDepth | null, options?: DetectColorOptions): Destination;
|
|
143
|
+
/**
|
|
144
|
+
* Resolve a string spec into a `ColorDepth` (or `null` for no color).
|
|
145
|
+
*
|
|
146
|
+
* `"auto"` triggers env-based detection; all other recognized specs are
|
|
147
|
+
* direct table lookups against `STRING_TO_DEPTH`. Throws on unknown specs
|
|
148
|
+
* — silent fallback would mask user typos.
|
|
149
|
+
*
|
|
150
|
+
* [LAW:single-enforcer] All string→ColorDepth resolution flows through here.
|
|
151
|
+
*/
|
|
152
|
+
export declare function resolveColorSystem(spec: string, options?: DetectColorOptions): ColorDepth | null;
|
|
153
|
+
export declare class ColorParseError extends Error {
|
|
154
|
+
constructor(message: string);
|
|
155
|
+
}
|
|
156
|
+
export declare class ColorSpec {
|
|
157
|
+
readonly name: string;
|
|
158
|
+
readonly type: ColorDepth;
|
|
159
|
+
readonly number: number | undefined;
|
|
160
|
+
readonly value: ColorRgba | undefined;
|
|
161
|
+
private downgradeCache;
|
|
162
|
+
constructor(name: string, type: ColorDepth, number?: number, value?: ColorRgba);
|
|
163
|
+
get isDefault(): boolean;
|
|
164
|
+
/**
|
|
165
|
+
* The colour every terminal draws this spec as, where that is fixed — before
|
|
166
|
+
* any alpha is flattened, so a translucent value is drawn composited over
|
|
167
|
+
* its ground (`flattenAlpha`) and should be measured that way: a
|
|
168
|
+
* truecolor value, or a 256-colour cube or grey-ramp entry (xterm fixes
|
|
169
|
+
* indices 16–255, and `fromAnsi` types only those as EIGHT_BIT). ANSI 0–15
|
|
170
|
+
* and the default colour are the terminal theme's own, so they have none.
|
|
171
|
+
*/
|
|
172
|
+
get fixedValue(): ColorRgba | undefined;
|
|
173
|
+
get isSystemDefined(): boolean;
|
|
174
|
+
/**
|
|
175
|
+
* Generates SGR parameter strings for this color.
|
|
176
|
+
*/
|
|
177
|
+
getAnsiCodes(foreground?: boolean): string[];
|
|
178
|
+
/**
|
|
179
|
+
* Composite this color's alpha (if any) against an opaque background,
|
|
180
|
+
* returning a fully-opaque ColorSpec. Non-truecolor specs (palette
|
|
181
|
+
* indices, default) have no alpha and pass through unchanged.
|
|
182
|
+
*
|
|
183
|
+
* [LAW:single-enforcer] Sole alpha-flattening site for the render
|
|
184
|
+
* pipeline. Run *before* downgrade — otherwise palette matching on the
|
|
185
|
+
* still-translucent RGB silently drops alpha (the "flatten then lose
|
|
186
|
+
* alpha" path the unification ticket forbids).
|
|
187
|
+
*
|
|
188
|
+
* [LAW:dataflow-not-control-flow] `ColorRgba.compositeOver` is a no-op
|
|
189
|
+
* when alpha=1, so callers invoke this unconditionally; the data
|
|
190
|
+
* (alpha value) decides whether work happens.
|
|
191
|
+
*/
|
|
192
|
+
flattenAlpha(bg: ColorRgba): ColorSpec;
|
|
193
|
+
/**
|
|
194
|
+
* Downgrade to a lower-fidelity color depth. Cached.
|
|
195
|
+
*/
|
|
196
|
+
downgrade(targetSystem: ColorDepth): ColorSpec;
|
|
197
|
+
/**
|
|
198
|
+
* Resolves to an actual RGB(A) value for any color type.
|
|
199
|
+
*/
|
|
200
|
+
getTruecolor(theme?: TerminalTheme, foreground?: boolean): ColorRgba;
|
|
201
|
+
static default(): ColorSpec;
|
|
202
|
+
static fromAnsi(n: number): ColorSpec;
|
|
203
|
+
static fromRgba(value: ColorRgba): ColorSpec;
|
|
204
|
+
static fromRgb(r: number, g: number, b: number): ColorSpec;
|
|
205
|
+
/**
|
|
206
|
+
* Parse a color string. Cached — identical strings return the same instance.
|
|
207
|
+
*/
|
|
208
|
+
static parse(colorString: string): ColorSpec;
|
|
209
|
+
private performDowngrade;
|
|
210
|
+
}
|
|
211
|
+
export declare function parseRgbHex(hex: string): ColorRgba;
|
|
212
|
+
export declare function parseRgbaHex(hex: string): ColorRgba;
|
|
213
|
+
export declare function blendRgb(color1: ColorRgba, color2: ColorRgba, crossFade?: number): ColorRgba;
|
|
214
|
+
/**
|
|
215
|
+
* A terminal theme — surface/foreground baseline, the ANSI 16/256 LUT, and a
|
|
216
|
+
* semantic palette.
|
|
217
|
+
*
|
|
218
|
+
* **`ansiColors` is the theme's own sixteen colours**, the shades a terminal
|
|
219
|
+
* showing this theme draws `red`, `blue` and the rest in. `ColorSpec.parse("red")`
|
|
220
|
+
* is ANSI colour 1 under every theme; which red that is, is the theme's to say,
|
|
221
|
+
* as it is in Rich, where each `TerminalTheme` carries its own table.
|
|
222
|
+
*/
|
|
223
|
+
export declare class TerminalTheme {
|
|
224
|
+
readonly backgroundColor: ColorRgba;
|
|
225
|
+
readonly foregroundColor: ColorRgba;
|
|
226
|
+
readonly ansiColors: ColorTable;
|
|
227
|
+
readonly palette: import("../themes/palette.js").Palette;
|
|
228
|
+
constructor(backgroundColor: ColorRgba, foregroundColor: ColorRgba, ansiColors: ColorTable, palette: import("../themes/palette.js").Palette);
|
|
229
|
+
}
|
|
230
|
+
export declare const STANDARD_TABLE: ColorTable;
|
|
231
|
+
export declare const EIGHT_BIT_TABLE: ColorTable;
|
|
232
|
+
/**
|
|
233
|
+
* What a downgrade to 256 colours may choose: the cube and the grey ramp,
|
|
234
|
+
* indices 16–255. Indices 0–15 are the terminal's own ANSI colours, which
|
|
235
|
+
* every theme redefines, so their RGB is unknown and a match against them is
|
|
236
|
+
* a guess; Python Rich never picks them either.
|
|
237
|
+
*/
|
|
238
|
+
export declare const EIGHT_BIT_DOWNGRADE_TABLE: ColorTable;
|
|
239
|
+
export declare const WINDOWS_TABLE: ColorTable;
|
|
240
|
+
export declare const ANSI_COLOR_NAMES: Record<string, number>;
|
|
241
|
+
/**
|
|
242
|
+
* Every word `ColorSpec.parse` accepts as a name rather than a `#hex`,
|
|
243
|
+
* `rgb()` or `color(N)` form: `default` and the named palette entries, which
|
|
244
|
+
* are exactly the two name checks `parseSingle` makes.
|
|
245
|
+
*/
|
|
246
|
+
export declare const COLOR_NAMES: readonly string[];
|