@alchemy.run/sigil 0.0.0-alpha.3 → 0.0.0-alpha.5
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 +23 -13
- package/THIRD_PARTY_NOTICES.md +70 -23
- package/dist/Text-D5HUf3Fj.d.ts +452 -0
- package/dist/ansi.d.ts +137 -143
- package/dist/ansi.js +87 -2
- package/dist/capabilities.d.ts +5 -0
- package/dist/capabilities.js +3 -0
- package/dist/cell-_ZVhbfl0.js +44 -0
- package/dist/color-CkbalRqK.js +2 -0
- package/dist/color-policy-BMzMwV7Q.d.ts +22 -0
- package/dist/color-policy-SVj1pYTA.js +560 -0
- package/dist/color-profile-DHhQHY55.js +36 -0
- package/dist/color-profile-u0Nhe9Nv.d.ts +97 -0
- package/dist/color.d.ts +21 -0
- package/dist/color.js +3 -0
- package/dist/cursor-position-D2LAkRG0.d.ts +7 -0
- package/dist/detect-Bh4yGP6w.d.ts +186 -0
- package/dist/detect-BuTXtY6e.js +373 -0
- package/dist/{devtools-BhYGjb7h.js → devtools-DbthxoD1.js} +22 -23
- package/dist/env-YVw64yZS.js +9 -0
- package/dist/escapes-CB_6CWOE.d.ts +72 -0
- package/dist/geometry-BxXOzJgo.d.ts +11 -0
- package/dist/index-Bmc2tRPk.d.ts +21 -0
- package/dist/index.d.ts +235 -754
- package/dist/index.js +1362 -3001
- package/dist/osc-CCH7xDoS.js +71 -0
- package/dist/osc-Cn0fw77g.d.ts +23 -0
- package/dist/paint-C19minOS.d.ts +81 -0
- package/dist/query-vaIeGOkH.d.ts +152 -0
- package/dist/router.d.ts +392 -0
- package/dist/router.js +709 -0
- package/dist/sample-Cqw1bjUL.js +445 -0
- package/dist/screen-BOh__-65.js +324 -0
- package/dist/screen-BReKIheE.d.ts +40 -0
- package/dist/screen.d.ts +5 -0
- package/dist/screen.js +5 -0
- package/dist/semantic-text-style-DIMzC7xt.js +91 -0
- package/dist/serialize-BTkAZgw1.js +79 -0
- package/dist/session-BJmzX2NJ.js +664 -0
- package/dist/sgr-BhwaWAJB.js +246 -0
- package/dist/store-CgrG9K4y.d.ts +72 -0
- package/dist/string-width-CijQwpIk.js +69 -0
- package/dist/strip-BvU4toXG.js +6 -0
- package/dist/terminal.d.ts +118 -0
- package/dist/terminal.js +2 -0
- package/dist/tokenize-AjqbvtiT.js +1242 -0
- package/dist/tokenize-Dx1y_l5H.d.ts +57 -0
- package/dist/truncate-D31fhU6i.js +562 -0
- package/dist/use-focus-C2sciZOo.js +1335 -0
- package/package.json +43 -17
- package/src/ansi/chalk.ts +12 -53
- package/src/ansi/cursor.ts +2 -4
- package/src/ansi/east-asian-width.ts +44 -0
- package/src/ansi/escapes.ts +18 -26
- package/src/ansi/graphemes.ts +8 -0
- package/src/ansi/hyperlink.ts +44 -0
- package/src/ansi/index.ts +14 -14
- package/src/ansi/osc.ts +77 -0
- package/src/ansi/sgr.ts +2 -5
- package/src/ansi/slice.ts +1 -1
- package/src/ansi/string-width.ts +91 -206
- package/src/ansi/strip.ts +13 -33
- package/src/ansi/tokenize.ts +96 -169
- package/src/ansi/truncate.ts +5 -3
- package/src/ansi/wrap.ts +13 -14
- package/src/ansi-tokenizer.ts +1 -1
- package/src/capabilities/color-policy.ts +34 -0
- package/src/capabilities/detect.ts +594 -0
- package/src/capabilities/index.ts +37 -0
- package/src/capabilities/query.ts +657 -0
- package/src/capabilities/store.ts +379 -0
- package/src/color/index.ts +3 -0
- package/src/color/paint.ts +169 -0
- package/src/color/palette.ts +48 -0
- package/src/color/sample.ts +323 -0
- package/src/color.ts +1 -0
- package/src/components/AnsiText.tsx +42 -0
- package/src/components/App.tsx +109 -22
- package/src/components/BackgroundContext.ts +2 -3
- package/src/components/Box.tsx +43 -60
- package/src/components/CursorContext.ts +1 -1
- package/src/components/ErrorBoundary.tsx +1 -1
- package/src/components/ErrorOverview.tsx +8 -7
- package/src/components/Hyperlink.tsx +56 -0
- package/src/components/Spacer.tsx +1 -1
- package/src/components/Static.tsx +1 -1
- package/src/components/StderrContext.ts +0 -2
- package/src/components/StdinContext.ts +0 -1
- package/src/components/StdoutContext.ts +1 -3
- package/src/components/TerminalOscContext.ts +25 -0
- package/src/components/Text.tsx +23 -46
- package/src/components/Transform.tsx +2 -2
- package/src/cursor-position.ts +1 -1
- package/src/devtools.ts +84 -24
- package/src/dom.ts +11 -8
- package/src/env.ts +12 -0
- package/src/get-max-width.ts +1 -1
- package/src/global.d.ts +6 -4
- package/src/{boxes.ts → glyphs.ts} +17 -18
- package/src/hooks/use-animation.ts +1 -1
- package/src/hooks/use-app.ts +1 -1
- package/src/hooks/use-box-metrics.ts +1 -1
- package/src/hooks/use-capabilities.ts +73 -0
- package/src/hooks/use-cursor.ts +3 -3
- package/src/hooks/use-focus-manager.ts +1 -1
- package/src/hooks/use-focus.ts +5 -6
- package/src/hooks/use-input.ts +4 -4
- package/src/hooks/use-is-screen-reader-enabled.ts +1 -1
- package/src/hooks/use-paste.ts +2 -2
- package/src/hooks/use-stderr.ts +1 -1
- package/src/hooks/use-stdin.ts +1 -1
- package/src/hooks/use-stdout.ts +1 -1
- package/src/hooks/use-terminal-osc.ts +59 -0
- package/src/hooks/use-window-size.ts +2 -2
- package/src/index.ts +88 -44
- package/src/ink.tsx +620 -745
- package/src/input-parser.ts +1 -1
- package/src/instances.ts +1 -1
- package/src/kitty-keyboard.ts +128 -0
- package/src/measure-element.ts +1 -1
- package/src/measure-text.ts +2 -2
- package/src/{render-node-to-output.ts → paint-tree.ts} +78 -53
- package/src/parse-keypress.ts +4 -5
- package/src/parse-stack-line.ts +0 -1
- package/src/patch-console.ts +44 -0
- package/src/reconciler.ts +36 -62
- package/src/render-background.ts +37 -16
- package/src/render-border.ts +97 -64
- package/src/render-frame.ts +83 -0
- package/src/render-to-string.ts +24 -9
- package/src/render.ts +24 -16
- package/src/router/components.tsx +343 -0
- package/src/router/context.ts +41 -0
- package/src/router/history.ts +194 -0
- package/src/router/hooks.tsx +391 -0
- package/src/router/index.ts +34 -0
- package/src/router/matcher.ts +571 -0
- package/src/sanitize-ansi.ts +1 -1
- package/src/screen/ansi.ts +184 -0
- package/src/screen/canvas.ts +137 -0
- package/src/screen/cell.ts +138 -0
- package/src/screen/color-profile.ts +47 -0
- package/src/screen/geometry.ts +9 -0
- package/src/screen/index.ts +6 -0
- package/src/screen/screen.ts +272 -0
- package/src/screen/serialize.ts +129 -0
- package/src/screen.ts +1 -0
- package/src/semantic-text-style.ts +118 -0
- package/src/signal-exit.ts +4 -5
- package/src/squash-text-nodes.ts +4 -7
- package/src/stream.ts +2 -4
- package/src/structured-text.ts +321 -0
- package/src/styles.ts +16 -17
- package/src/terminal/index.ts +2 -0
- package/src/terminal/inline-presenter.ts +120 -0
- package/src/terminal/input.ts +86 -0
- package/src/terminal/render-scheduler.ts +37 -0
- package/src/terminal/screen-presenter.ts +188 -0
- package/src/terminal/session.ts +407 -0
- package/src/terminal-size.ts +9 -8
- package/src/terminal.ts +1 -0
- package/src/testing/browser.ts +588 -0
- package/src/testing/emulators.ts +205 -0
- package/src/testing/explorer-app/index.html +12 -0
- package/src/testing/explorer-app/main.ts +381 -0
- package/src/testing/explorer-app/style.css +194 -0
- package/src/testing/explorer-app/tsconfig.json +15 -0
- package/src/testing/explorer-app/vite-env.d.ts +1 -0
- package/src/testing/index.ts +26 -0
- package/src/testing/keys.ts +56 -0
- package/src/testing/live.ts +85 -0
- package/src/testing/matchers.ts +70 -0
- package/src/testing/public.ts +94 -0
- package/src/testing/terminal.ts +349 -0
- package/src/testing/vitest.ts +157 -0
- package/src/transform-adapter.ts +14 -0
- package/src/types.ts +1 -6
- package/src/utils.ts +2 -2
- package/src/wrap-text.ts +4 -4
- package/src/yoga/config.ts +2 -2
- package/src/yoga/core/absoluteLayout.ts +15 -15
- package/src/yoga/core/baseline.ts +3 -3
- package/src/yoga/core/cache.ts +4 -4
- package/src/yoga/core/calculateLayout.ts +33 -27
- package/src/yoga/core/config.ts +1 -1
- package/src/yoga/core/flexLine.ts +3 -3
- package/src/yoga/core/helpers.ts +3 -3
- package/src/yoga/core/layoutResults.ts +4 -4
- package/src/yoga/core/node.ts +16 -16
- package/src/yoga/core/pixelGrid.ts +4 -4
- package/src/yoga/core/style.ts +11 -11
- package/src/yoga/core/types.ts +2 -2
- package/src/yoga/index.ts +6 -6
- package/src/yoga/node.ts +8 -8
- package/dist/sgr-CMfEpjSk.d.ts +0 -91
- package/dist/truncate-CBiyyZzw.js +0 -2156
- package/src/ansi/supports-color.ts +0 -213
- package/src/ansi/widest-line.ts +0 -12
- package/src/auto-bind.ts +0 -41
- package/src/colorize.ts +0 -60
- package/src/devtools-window-polyfill.ts +0 -73
- package/src/indent-string.ts +0 -16
- package/src/is-in-ci.ts +0 -7
- package/src/log-update.ts +0 -370
- package/src/output.ts +0 -308
- package/src/renderer.ts +0 -73
- package/src/write-synchronized.ts +0 -9
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import {
|
|
2
|
+
rgb,
|
|
3
|
+
type GradientStop,
|
|
4
|
+
type InterpolationSpace,
|
|
5
|
+
type LinearGradient,
|
|
6
|
+
type Paint,
|
|
7
|
+
type PaintContext,
|
|
8
|
+
type PerimeterGradient,
|
|
9
|
+
} from "#/color/paint.ts";
|
|
10
|
+
import { colorToRgb } from "#/color/palette.ts";
|
|
11
|
+
import type { Color, RgbColor } from "#/screen/cell.ts";
|
|
12
|
+
import type { Rect } from "#/screen/geometry.ts";
|
|
13
|
+
import { parseSemanticColor } from "#/semantic-text-style.ts";
|
|
14
|
+
|
|
15
|
+
export function samplePaint(
|
|
16
|
+
paint: Paint,
|
|
17
|
+
x: number,
|
|
18
|
+
y: number,
|
|
19
|
+
bounds: Rect,
|
|
20
|
+
context: PaintContext = {},
|
|
21
|
+
): Color | undefined {
|
|
22
|
+
const resolved = resolveConditional(paint, context);
|
|
23
|
+
if (resolved && typeof resolved === "object" && "type" in resolved) {
|
|
24
|
+
if (resolved.type === "perimeter-gradient") {
|
|
25
|
+
return samplePerimeterGradient(resolved, x, y, bounds, context);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (isLinearGradient(resolved)) {
|
|
29
|
+
return sampleGradient(resolved, x, y, bounds, context);
|
|
30
|
+
}
|
|
31
|
+
return resolveColor(resolved);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function blend(source: Color, destination: Color, palette?: PaintContext["palette"]): Color {
|
|
35
|
+
const foreground = colorToRgb(source, palette);
|
|
36
|
+
if (foreground.alpha >= 255) return foreground;
|
|
37
|
+
const background = colorToRgb(destination, palette);
|
|
38
|
+
const alpha = foreground.alpha / 255;
|
|
39
|
+
return rgb(
|
|
40
|
+
foreground.red * alpha + background.red * (1 - alpha),
|
|
41
|
+
foreground.green * alpha + background.green * (1 - alpha),
|
|
42
|
+
foreground.blue * alpha + background.blue * (1 - alpha),
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Progressively moves a color toward white in perceptual OKLab space. */
|
|
47
|
+
export function lighten(color: Color, amount: number, palette?: PaintContext["palette"]): RgbColor {
|
|
48
|
+
return interpolateColor(color, rgb(255, 255, 255), amount, "oklab", palette);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Progressively moves a color toward black in perceptual OKLab space. */
|
|
52
|
+
export function darken(color: Color, amount: number, palette?: PaintContext["palette"]): RgbColor {
|
|
53
|
+
return interpolateColor(color, rgb(0, 0, 0), amount, "oklab", palette);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function sampleGradient(
|
|
57
|
+
gradient: LinearGradient,
|
|
58
|
+
x: number,
|
|
59
|
+
y: number,
|
|
60
|
+
bounds: Rect,
|
|
61
|
+
context: PaintContext,
|
|
62
|
+
): Color | undefined {
|
|
63
|
+
const radians = (gradient.angle * Math.PI) / 180;
|
|
64
|
+
const dx = Math.cos(radians);
|
|
65
|
+
const dy = Math.sin(radians);
|
|
66
|
+
const width = Math.max(1, bounds.width - 1);
|
|
67
|
+
const height = Math.max(1, bounds.height - 1);
|
|
68
|
+
const projections = [0, width * dx, height * dy, width * dx + height * dy];
|
|
69
|
+
const minimum = Math.min(...projections);
|
|
70
|
+
const maximum = Math.max(...projections);
|
|
71
|
+
const projection = (x - bounds.x) * dx + (y - bounds.y) * dy;
|
|
72
|
+
const position = maximum === minimum ? 0 : (projection - minimum) / (maximum - minimum);
|
|
73
|
+
return sampleStops(gradient.stops, position, gradient.space, x, y, bounds, context);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function samplePerimeterGradient(
|
|
77
|
+
gradient: PerimeterGradient,
|
|
78
|
+
x: number,
|
|
79
|
+
y: number,
|
|
80
|
+
bounds: Rect,
|
|
81
|
+
context: PaintContext,
|
|
82
|
+
): Color | undefined {
|
|
83
|
+
const width = Math.max(1, bounds.width);
|
|
84
|
+
const height = Math.max(1, bounds.height);
|
|
85
|
+
const steps = width === 1 || height === 1 ? Math.max(width, height) : 2 * width + 2 * height - 4;
|
|
86
|
+
if (steps <= 1) return samplePaint(gradient.stops[0]!.color, x, y, bounds, context);
|
|
87
|
+
|
|
88
|
+
const left = bounds.x;
|
|
89
|
+
const top = bounds.y;
|
|
90
|
+
const right = left + width - 1;
|
|
91
|
+
const bottom = top + height - 1;
|
|
92
|
+
let index: number;
|
|
93
|
+
|
|
94
|
+
if (height === 1) index = x - left;
|
|
95
|
+
else if (width === 1) index = y - top;
|
|
96
|
+
else if (y <= top) index = Math.max(0, Math.min(width - 1, x - left));
|
|
97
|
+
else if (x >= right) index = width + Math.max(0, Math.min(height - 2, y - top - 1));
|
|
98
|
+
else if (y >= bottom) index = width + height - 2 + Math.max(0, Math.min(width - 1, right - x));
|
|
99
|
+
else index = 2 * width + height - 2 + Math.max(0, Math.min(height - 2, bottom - y - 1));
|
|
100
|
+
|
|
101
|
+
index = modulo(index - gradient.offset, steps);
|
|
102
|
+
const position = blendPosition(index, steps, gradient.stops.length);
|
|
103
|
+
return sampleStops(gradient.stops, position, gradient.space, x, y, bounds, context);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Matches Lip Gloss Blend1D's even, discrete distribution of stops. */
|
|
107
|
+
function blendPosition(index: number, steps: number, stopCount: number): number {
|
|
108
|
+
if (steps <= stopCount) return index / Math.max(1, stopCount - 1);
|
|
109
|
+
const segments = stopCount - 1;
|
|
110
|
+
const baseSize = Math.floor(steps / segments);
|
|
111
|
+
const remainder = steps % segments;
|
|
112
|
+
let start = 0;
|
|
113
|
+
for (let segment = 0; segment < segments; segment++) {
|
|
114
|
+
const size = baseSize + (segment < remainder ? 1 : 0);
|
|
115
|
+
if (index < start + size) {
|
|
116
|
+
const local = size <= 1 ? 0 : (index - start) / (size - 1);
|
|
117
|
+
return (segment + local) / segments;
|
|
118
|
+
}
|
|
119
|
+
start += size;
|
|
120
|
+
}
|
|
121
|
+
return 1;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function sampleStops(
|
|
125
|
+
stops: readonly GradientStop[],
|
|
126
|
+
position: number,
|
|
127
|
+
space: InterpolationSpace,
|
|
128
|
+
x: number,
|
|
129
|
+
y: number,
|
|
130
|
+
bounds: Rect,
|
|
131
|
+
context: PaintContext,
|
|
132
|
+
): Color | undefined {
|
|
133
|
+
let right = 1;
|
|
134
|
+
while (right < stops.length - 1 && position > stops[right]!.offset!) right++;
|
|
135
|
+
const left = right - 1;
|
|
136
|
+
const start = stops[left]!;
|
|
137
|
+
const end = stops[right]!;
|
|
138
|
+
const span = end.offset! - start.offset!;
|
|
139
|
+
const amount = span === 0 ? 0 : Math.max(0, Math.min(1, (position - start.offset!) / span));
|
|
140
|
+
const startColor = samplePaint(start.color, x, y, bounds, context);
|
|
141
|
+
const endColor = samplePaint(end.color, x, y, bounds, context);
|
|
142
|
+
if (!startColor || !endColor) return startColor ?? endColor;
|
|
143
|
+
return interpolateColor(startColor, endColor, amount, space, context.palette);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function interpolateColor(
|
|
147
|
+
from: Color,
|
|
148
|
+
to: Color,
|
|
149
|
+
amount: number,
|
|
150
|
+
space: InterpolationSpace = "oklab",
|
|
151
|
+
palette?: PaintContext["palette"],
|
|
152
|
+
): RgbColor {
|
|
153
|
+
const a = colorToRgb(from, palette);
|
|
154
|
+
const b = colorToRgb(to, palette);
|
|
155
|
+
const t = Math.max(0, Math.min(1, amount));
|
|
156
|
+
if (space === "rgb")
|
|
157
|
+
return rgb(
|
|
158
|
+
mix(a.red, b.red, t),
|
|
159
|
+
mix(a.green, b.green, t),
|
|
160
|
+
mix(a.blue, b.blue, t),
|
|
161
|
+
mix(a.alpha, b.alpha, t),
|
|
162
|
+
);
|
|
163
|
+
if (space === "hsv") {
|
|
164
|
+
const ah = toHsv(a),
|
|
165
|
+
bh = toHsv(b);
|
|
166
|
+
let delta = bh[0] - ah[0];
|
|
167
|
+
if (delta > 180) delta -= 360;
|
|
168
|
+
if (delta < -180) delta += 360;
|
|
169
|
+
return fromHsv(
|
|
170
|
+
ah[0] + delta * t,
|
|
171
|
+
mix(ah[1], bh[1], t),
|
|
172
|
+
mix(ah[2], bh[2], t),
|
|
173
|
+
mix(a.alpha, b.alpha, t),
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
if (space === "lab") {
|
|
177
|
+
const al = toLab(a),
|
|
178
|
+
bl = toLab(b);
|
|
179
|
+
return fromLab(
|
|
180
|
+
mix(al[0], bl[0], t),
|
|
181
|
+
mix(al[1], bl[1], t),
|
|
182
|
+
mix(al[2], bl[2], t),
|
|
183
|
+
mix(a.alpha, b.alpha, t),
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
const al = toOklab(a),
|
|
187
|
+
bl = toOklab(b);
|
|
188
|
+
return fromOklab(
|
|
189
|
+
mix(al[0], bl[0], t),
|
|
190
|
+
mix(al[1], bl[1], t),
|
|
191
|
+
mix(al[2], bl[2], t),
|
|
192
|
+
mix(a.alpha, b.alpha, t),
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const D65 = [0.95047, 1, 1.08883] as const;
|
|
197
|
+
function labCurve(value: number): number {
|
|
198
|
+
return value > (6 / 29) ** 3 ? Math.cbrt(value) : (value / 3) * (29 / 6) ** 2 + 4 / 29;
|
|
199
|
+
}
|
|
200
|
+
function inverseLabCurve(value: number): number {
|
|
201
|
+
return value > 6 / 29 ? value ** 3 : 3 * (6 / 29) ** 2 * (value - 4 / 29);
|
|
202
|
+
}
|
|
203
|
+
function toLab(color: RgbColor): [number, number, number] {
|
|
204
|
+
const r = linear(color.red),
|
|
205
|
+
g = linear(color.green),
|
|
206
|
+
b = linear(color.blue);
|
|
207
|
+
const x = 0.4123907992659595 * r + 0.357584339383878 * g + 0.1804807884018343 * b;
|
|
208
|
+
const y = 0.2126390058715104 * r + 0.7151686787677559 * g + 0.0721923153607337 * b;
|
|
209
|
+
const z = 0.0193308187155919 * r + 0.119194779794626 * g + 0.9505321522496606 * b;
|
|
210
|
+
const fy = labCurve(y / D65[1]);
|
|
211
|
+
return [1.16 * fy - 0.16, 5 * (labCurve(x / D65[0]) - fy), 2 * (fy - labCurve(z / D65[2]))];
|
|
212
|
+
}
|
|
213
|
+
function fromLab(l: number, a: number, b: number, alpha: number): RgbColor {
|
|
214
|
+
const center = (l + 0.16) / 1.16;
|
|
215
|
+
const x = D65[0] * inverseLabCurve(center + a / 5);
|
|
216
|
+
const y = D65[1] * inverseLabCurve(center);
|
|
217
|
+
const z = D65[2] * inverseLabCurve(center - b / 2);
|
|
218
|
+
return rgb(
|
|
219
|
+
colorfulChannel(3.240969941904521 * x - 1.5373831775700935 * y - 0.4986107602930033 * z),
|
|
220
|
+
colorfulChannel(-0.9692436362808798 * x + 1.8759675015077206 * y + 0.0415550574071756 * z),
|
|
221
|
+
colorfulChannel(0.0556300796969936 * x - 0.2039769588889766 * y + 1.0569715142428786 * z),
|
|
222
|
+
alpha,
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const mix = (a: number, b: number, t: number) => a + (b - a) * t;
|
|
227
|
+
const linear = (value: number) => {
|
|
228
|
+
const x = value / 255;
|
|
229
|
+
return x <= 0.04045 ? x / 12.92 : ((x + 0.055) / 1.055) ** 2.4;
|
|
230
|
+
};
|
|
231
|
+
const gamma = (value: number) =>
|
|
232
|
+
255 * (value <= 0.0031308 ? 12.92 * value : 1.055 * value ** (1 / 2.4) - 0.055);
|
|
233
|
+
const colorfulChannel = (value: number) => {
|
|
234
|
+
const encoded = value <= 0.0031308 ? 12.92 * value : 1.055 * value ** (1 / 2.4) - 0.055;
|
|
235
|
+
return Math.floor(Math.round(Math.max(0, Math.min(1, encoded)) * 65_535) / 256);
|
|
236
|
+
};
|
|
237
|
+
function toOklab(c: RgbColor): [number, number, number] {
|
|
238
|
+
const r = linear(c.red),
|
|
239
|
+
g = linear(c.green),
|
|
240
|
+
b = linear(c.blue);
|
|
241
|
+
const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b),
|
|
242
|
+
m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b),
|
|
243
|
+
s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
|
|
244
|
+
return [
|
|
245
|
+
0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s,
|
|
246
|
+
1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s,
|
|
247
|
+
0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s,
|
|
248
|
+
];
|
|
249
|
+
}
|
|
250
|
+
function fromOklab(L: number, a: number, b: number, alpha: number): RgbColor {
|
|
251
|
+
const l = (L + 0.3963377774 * a + 0.2158037573 * b) ** 3,
|
|
252
|
+
m = (L - 0.1055613458 * a - 0.0638541728 * b) ** 3,
|
|
253
|
+
s = (L - 0.0894841775 * a - 1.291485548 * b) ** 3;
|
|
254
|
+
return rgb(
|
|
255
|
+
gamma(4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s),
|
|
256
|
+
gamma(-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s),
|
|
257
|
+
gamma(-0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s),
|
|
258
|
+
alpha,
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
function toHsv(c: RgbColor): [number, number, number] {
|
|
262
|
+
const r = c.red / 255,
|
|
263
|
+
g = c.green / 255,
|
|
264
|
+
b = c.blue / 255,
|
|
265
|
+
max = Math.max(r, g, b),
|
|
266
|
+
min = Math.min(r, g, b),
|
|
267
|
+
d = max - min;
|
|
268
|
+
let h = 0;
|
|
269
|
+
if (d)
|
|
270
|
+
h =
|
|
271
|
+
max === r
|
|
272
|
+
? 60 * (((g - b) / d) % 6)
|
|
273
|
+
: max === g
|
|
274
|
+
? 60 * ((b - r) / d + 2)
|
|
275
|
+
: 60 * ((r - g) / d + 4);
|
|
276
|
+
return [(h + 360) % 360, max === 0 ? 0 : d / max, max];
|
|
277
|
+
}
|
|
278
|
+
function fromHsv(h: number, s: number, v: number, alpha: number): RgbColor {
|
|
279
|
+
const c = v * s,
|
|
280
|
+
x = c * (1 - Math.abs(((h / 60) % 2) - 1)),
|
|
281
|
+
m = v - c;
|
|
282
|
+
const [r, g, b] =
|
|
283
|
+
h < 60
|
|
284
|
+
? [c, x, 0]
|
|
285
|
+
: h < 120
|
|
286
|
+
? [x, c, 0]
|
|
287
|
+
: h < 180
|
|
288
|
+
? [0, c, x]
|
|
289
|
+
: h < 240
|
|
290
|
+
? [0, x, c]
|
|
291
|
+
: h < 300
|
|
292
|
+
? [x, 0, c]
|
|
293
|
+
: [c, 0, x];
|
|
294
|
+
return rgb((r + m) * 255, (g + m) * 255, (b + m) * 255, alpha);
|
|
295
|
+
}
|
|
296
|
+
function resolveConditional(paint: Paint, context: PaintContext): Paint {
|
|
297
|
+
if (typeof paint !== "object" || paint === null || "model" in paint) return paint;
|
|
298
|
+
if (paint.type === "adaptive-color")
|
|
299
|
+
return resolveConditional(context.appearance === "light" ? paint.light : paint.dark, context);
|
|
300
|
+
if (paint.type === "profile-color")
|
|
301
|
+
return resolveConditional(
|
|
302
|
+
(context.profile && paint.colors[context.profile]) ?? paint.fallback,
|
|
303
|
+
context,
|
|
304
|
+
);
|
|
305
|
+
return paint;
|
|
306
|
+
}
|
|
307
|
+
function resolveColor(value: Paint): Color | undefined {
|
|
308
|
+
return typeof value === "string"
|
|
309
|
+
? parseSemanticColor(value)
|
|
310
|
+
: typeof value === "object" && value !== null && "model" in value
|
|
311
|
+
? value
|
|
312
|
+
: undefined;
|
|
313
|
+
}
|
|
314
|
+
function isLinearGradient(value: Paint): value is LinearGradient {
|
|
315
|
+
return (
|
|
316
|
+
typeof value === "object" &&
|
|
317
|
+
value !== null &&
|
|
318
|
+
"type" in value &&
|
|
319
|
+
value.type === "linear-gradient"
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const modulo = (value: number, divisor: number) => ((value % divisor) + divisor) % divisor;
|
package/src/color.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "#/color/index.ts";
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/** @jsxImportSource react */
|
|
2
|
+
import { useContext } from "react";
|
|
3
|
+
|
|
4
|
+
import { stripAnsi } from "#/ansi/strip.ts";
|
|
5
|
+
import { accessibilityContext } from "#/components/AccessibilityContext.ts";
|
|
6
|
+
import { Text } from "#/components/Text.tsx";
|
|
7
|
+
import type { Styles } from "#/styles.ts";
|
|
8
|
+
|
|
9
|
+
export type Props = {
|
|
10
|
+
/** External text containing ANSI SGR styling or OSC 8 hyperlinks. */
|
|
11
|
+
readonly children: string;
|
|
12
|
+
readonly wrap?: Styles["textWrap"];
|
|
13
|
+
readonly "aria-label"?: string;
|
|
14
|
+
readonly "aria-hidden"?: boolean;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Renders explicitly trusted ANSI-styled output as structured terminal cells.
|
|
19
|
+
* Ordinary `Text` continues to strip terminal control sequences.
|
|
20
|
+
*/
|
|
21
|
+
export function AnsiText({
|
|
22
|
+
children,
|
|
23
|
+
wrap = "wrap",
|
|
24
|
+
"aria-label": ariaLabel,
|
|
25
|
+
"aria-hidden": ariaHidden = false,
|
|
26
|
+
}: Props) {
|
|
27
|
+
const { isScreenReaderEnabled } = useContext(accessibilityContext);
|
|
28
|
+
|
|
29
|
+
if (isScreenReaderEnabled) {
|
|
30
|
+
if (ariaHidden) return null;
|
|
31
|
+
return <Text>{ariaLabel ?? stripAnsi(children)}</Text>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<ink-text
|
|
36
|
+
style={{ flexGrow: 0, flexShrink: 1, flexDirection: "row", textWrap: wrap }}
|
|
37
|
+
internal_ansi
|
|
38
|
+
>
|
|
39
|
+
{children}
|
|
40
|
+
</ink-text>
|
|
41
|
+
);
|
|
42
|
+
}
|
package/src/components/App.tsx
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/** @jsxImportSource react */
|
|
2
2
|
import { EventEmitter } from "node:events";
|
|
3
|
-
import process from "node:process";
|
|
4
3
|
|
|
5
4
|
import React, {
|
|
6
5
|
type ReactNode,
|
|
@@ -12,19 +11,26 @@ import React, {
|
|
|
12
11
|
useInsertionEffect,
|
|
13
12
|
} from "react";
|
|
14
13
|
|
|
15
|
-
import { cliCursor } from "
|
|
16
|
-
import { ansiEscapes, CSI, ESC } from "
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
14
|
+
import { cliCursor } from "#/ansi/cursor.ts";
|
|
15
|
+
import { ansiEscapes, CSI, ESC } from "#/ansi/escapes.ts";
|
|
16
|
+
import {
|
|
17
|
+
ensureTerminalQuery,
|
|
18
|
+
getTerminalQueryPromise,
|
|
19
|
+
refreshTerminalQuery,
|
|
20
|
+
type TerminalQueryResult,
|
|
21
|
+
} from "#/capabilities/query.ts";
|
|
22
|
+
import { registerTerminalIntegration } from "#/capabilities/store.ts";
|
|
23
|
+
import { animationContext as AnimationContext } from "#/components/AnimationContext.ts";
|
|
24
|
+
import { AppContext, type SuspendTerminal } from "#/components/AppContext.ts";
|
|
25
|
+
import { CursorContext } from "#/components/CursorContext.ts";
|
|
26
|
+
import { ErrorBoundary } from "#/components/ErrorBoundary.tsx";
|
|
27
|
+
import { FocusContext } from "#/components/FocusContext.ts";
|
|
28
|
+
import { StderrContext } from "#/components/StderrContext.ts";
|
|
29
|
+
import { StdinContext } from "#/components/StdinContext.ts";
|
|
30
|
+
import { StdoutContext } from "#/components/StdoutContext.ts";
|
|
31
|
+
import { type CursorPosition } from "#/cursor-position.ts";
|
|
32
|
+
import { getRawModeStream, type OutputStream } from "#/stream.ts";
|
|
33
|
+
import type { TerminalInput } from "#/terminal/input.ts";
|
|
28
34
|
|
|
29
35
|
const tab = "\t";
|
|
30
36
|
const shiftTab = `${CSI}Z`;
|
|
@@ -52,6 +58,7 @@ type Props = {
|
|
|
52
58
|
readonly setCursorPosition: (position: CursorPosition | undefined) => void;
|
|
53
59
|
readonly interactive: boolean;
|
|
54
60
|
readonly renderThrottleMs: number;
|
|
61
|
+
readonly terminalInput: TerminalInput;
|
|
55
62
|
};
|
|
56
63
|
|
|
57
64
|
type Focusable = {
|
|
@@ -77,6 +84,7 @@ export function App({
|
|
|
77
84
|
setCursorPosition,
|
|
78
85
|
interactive,
|
|
79
86
|
renderThrottleMs,
|
|
87
|
+
terminalInput,
|
|
80
88
|
}: Props): React.ReactNode {
|
|
81
89
|
const [isFocusEnabled, setIsFocusEnabled] = useState(true);
|
|
82
90
|
const [activeFocusId, setActiveFocusId] = useState<string | undefined>(undefined);
|
|
@@ -101,7 +109,6 @@ export function App({
|
|
|
101
109
|
internal_eventEmitter.current.setMaxListeners(Infinity);
|
|
102
110
|
// Store the currently attached readable listener to avoid stale closure issues
|
|
103
111
|
const readableListenerRef = useRef<(() => void) | undefined>(undefined);
|
|
104
|
-
const inputParserRef = useRef(createInputParser());
|
|
105
112
|
const pendingInputFlushRef = useRef<NodeJS.Timeout | undefined>(undefined);
|
|
106
113
|
// Small delay to let chunked escape sequences complete before flushing as literal input.
|
|
107
114
|
const pendingInputFlushDelayMilliseconds = 20;
|
|
@@ -215,10 +222,10 @@ export function App({
|
|
|
215
222
|
}, [stdin]);
|
|
216
223
|
|
|
217
224
|
const clearInputState = useCallback((): void => {
|
|
218
|
-
|
|
225
|
+
terminalInput.reset();
|
|
219
226
|
clearPendingInputFlush();
|
|
220
227
|
detachReadableListener();
|
|
221
|
-
}, [clearPendingInputFlush, detachReadableListener]);
|
|
228
|
+
}, [clearPendingInputFlush, detachReadableListener, terminalInput]);
|
|
222
229
|
|
|
223
230
|
const disableRawMode = useCallback((): void => {
|
|
224
231
|
if (!rawModeStdin) {
|
|
@@ -275,21 +282,21 @@ export function App({
|
|
|
275
282
|
clearPendingInputFlush();
|
|
276
283
|
pendingInputFlushRef.current = setTimeout(() => {
|
|
277
284
|
pendingInputFlushRef.current = undefined;
|
|
278
|
-
const pendingEscape =
|
|
285
|
+
const pendingEscape = terminalInput.flushPendingEscape();
|
|
279
286
|
if (!pendingEscape) {
|
|
280
287
|
return;
|
|
281
288
|
}
|
|
282
289
|
|
|
283
290
|
emitInput(pendingEscape);
|
|
284
291
|
}, pendingInputFlushDelayMilliseconds);
|
|
285
|
-
}, [clearPendingInputFlush, emitInput]);
|
|
292
|
+
}, [clearPendingInputFlush, emitInput, terminalInput]);
|
|
286
293
|
|
|
287
294
|
const handleReadable = useCallback((): void => {
|
|
288
295
|
clearPendingInputFlush();
|
|
289
296
|
let chunk;
|
|
290
297
|
// eslint-disable-next-line @typescript-eslint/no-restricted-types
|
|
291
298
|
while ((chunk = stdin.read() as string | null) !== null) {
|
|
292
|
-
const inputEvents =
|
|
299
|
+
const inputEvents = terminalInput.push(chunk);
|
|
293
300
|
for (const event of inputEvents) {
|
|
294
301
|
if (typeof event === "string") {
|
|
295
302
|
emitInput(event);
|
|
@@ -306,10 +313,10 @@ export function App({
|
|
|
306
313
|
}
|
|
307
314
|
}
|
|
308
315
|
|
|
309
|
-
if (
|
|
316
|
+
if (terminalInput.hasPendingEscape()) {
|
|
310
317
|
schedulePendingInputFlush();
|
|
311
318
|
}
|
|
312
|
-
}, [stdin, emitInput, clearPendingInputFlush, schedulePendingInputFlush]);
|
|
319
|
+
}, [stdin, emitInput, clearPendingInputFlush, schedulePendingInputFlush, terminalInput]);
|
|
313
320
|
|
|
314
321
|
const attachReadableListener = useCallback((): void => {
|
|
315
322
|
if (readableListenerRef.current) {
|
|
@@ -380,6 +387,86 @@ export function App({
|
|
|
380
387
|
[rawModeStdin, stdin, attachReadableListener, clearInputState, disableRawMode],
|
|
381
388
|
);
|
|
382
389
|
|
|
390
|
+
const handleQueryTerminal = useCallback(
|
|
391
|
+
async ({ refresh = false } = {}): Promise<TerminalQueryResult | undefined> => {
|
|
392
|
+
if (!interactive || !isRawModeSupported || !stdout.isTTY) {
|
|
393
|
+
return undefined;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Without a refresh, an existing (or in-flight) query answers directly.
|
|
397
|
+
if (!refresh) {
|
|
398
|
+
const existing = getTerminalQueryPromise(stdout);
|
|
399
|
+
if (existing) {
|
|
400
|
+
return existing;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Own the input stream for the duration of the query: raw mode on so
|
|
405
|
+
// responses arrive unbuffered, and the readable listener detached so they
|
|
406
|
+
// flow to the query's own data listener instead of being parsed as key
|
|
407
|
+
// presses. queryTerminal buffers real user input typed during the query
|
|
408
|
+
// and unshifts it back; reattaching the readable listener delivers it.
|
|
409
|
+
//
|
|
410
|
+
// Note: if kitty keyboard auto-detection is in flight at the same time,
|
|
411
|
+
// both data listeners see the same bytes; that detector answers the same
|
|
412
|
+
// CSI ? u probe this query sends, so it converges to the same result.
|
|
413
|
+
handleSetRawMode(true);
|
|
414
|
+
detachReadableListener();
|
|
415
|
+
try {
|
|
416
|
+
return await (refresh
|
|
417
|
+
? refreshTerminalQuery(stdin, stdout)
|
|
418
|
+
: ensureTerminalQuery(stdin, stdout));
|
|
419
|
+
} finally {
|
|
420
|
+
attachReadableListener();
|
|
421
|
+
handleSetRawMode(false);
|
|
422
|
+
}
|
|
423
|
+
},
|
|
424
|
+
[
|
|
425
|
+
interactive,
|
|
426
|
+
isRawModeSupported,
|
|
427
|
+
stdin,
|
|
428
|
+
stdout,
|
|
429
|
+
handleSetRawMode,
|
|
430
|
+
detachReadableListener,
|
|
431
|
+
attachReadableListener,
|
|
432
|
+
],
|
|
433
|
+
);
|
|
434
|
+
|
|
435
|
+
// While the store has push reporting enabled, someone must actually read
|
|
436
|
+
// stdin so the reports reach `ingest` — even in apps without a single
|
|
437
|
+
// `useInput`. Holding raw mode (ref-counted, like `useInput` does) keeps
|
|
438
|
+
// the readable pipeline attached for the duration.
|
|
439
|
+
const reportFeedActiveRef = useRef(false);
|
|
440
|
+
const handleSetReportFeed = useCallback(
|
|
441
|
+
(active: boolean): void => {
|
|
442
|
+
if (active === reportFeedActiveRef.current || !isRawModeSupported) {
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
reportFeedActiveRef.current = active;
|
|
447
|
+
handleSetRawMode(active);
|
|
448
|
+
},
|
|
449
|
+
[isRawModeSupported, handleSetRawMode],
|
|
450
|
+
);
|
|
451
|
+
|
|
452
|
+
// Hand the capabilities store this instance's integration: queries go
|
|
453
|
+
// through the input pipeline above instead of competing with it, and the
|
|
454
|
+
// report feed keeps that pipeline running while reports are enabled. An
|
|
455
|
+
// insertion effect runs before every passive effect — parent and child —
|
|
456
|
+
// so a child calling store.query() from its own mount effect always finds
|
|
457
|
+
// the integration already registered.
|
|
458
|
+
useInsertionEffect(() => {
|
|
459
|
+
const unregister = registerTerminalIntegration(stdout, {
|
|
460
|
+
runQuery: handleQueryTerminal,
|
|
461
|
+
setReportFeed: handleSetReportFeed,
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
return () => {
|
|
465
|
+
unregister();
|
|
466
|
+
handleSetReportFeed(false);
|
|
467
|
+
};
|
|
468
|
+
}, [stdout, handleQueryTerminal, handleSetReportFeed]);
|
|
469
|
+
|
|
383
470
|
const handleSetBracketedPasteMode = useCallback(
|
|
384
471
|
(isEnabled: boolean): void => {
|
|
385
472
|
if (!stdout.isTTY) {
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { createContext } from "react";
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import { type LiteralUnion } from "../types.ts";
|
|
3
|
+
import type { Paint } from "#/color/paint.ts";
|
|
5
4
|
|
|
6
|
-
export type BackgroundColor =
|
|
5
|
+
export type BackgroundColor = Paint;
|
|
7
6
|
|
|
8
7
|
export const backgroundContext = createContext<BackgroundColor | undefined>(undefined);
|