@alchemy.run/sigil 0.0.0-alpha.4 → 0.0.0-alpha.6
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 +21 -9
- package/THIRD_PARTY_NOTICES.md +39 -1
- package/dist/Text-BobFKi74.d.ts +452 -0
- package/dist/ansi.d.ts +122 -131
- package/dist/ansi.js +86 -6
- 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/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-DZ88EXJv.d.ts +21 -0
- package/dist/index.d.ts +143 -498
- package/dist/index.js +1026 -2244
- 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-BOSLQ8fF.d.ts +49 -0
- package/dist/screen-CiPytswf.js +342 -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-aZr9O8h3.js +665 -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-BzqAJi0n.js +1337 -0
- package/package.json +41 -9
- package/src/ansi/chalk.ts +5 -3
- package/src/ansi/escapes.ts +14 -0
- package/src/ansi/graphemes.ts +8 -0
- package/src/ansi/hyperlink.ts +44 -0
- package/src/ansi/index.ts +3 -1
- package/src/ansi/osc.ts +77 -0
- package/src/ansi/tokenize.ts +3 -4
- 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 +98 -10
- package/src/components/BackgroundContext.ts +2 -3
- package/src/components/Box.tsx +0 -8
- package/src/components/CursorContext.ts +1 -1
- package/src/components/Hyperlink.tsx +56 -0
- package/src/components/TerminalOscContext.ts +25 -0
- package/src/components/Text.tsx +22 -45
- package/src/components/Transform.tsx +1 -1
- package/src/dom.ts +11 -2
- package/src/global.d.ts +3 -0
- package/src/hooks/use-capabilities.ts +73 -0
- package/src/hooks/use-cursor.ts +2 -2
- package/src/hooks/use-terminal-osc.ts +59 -0
- package/src/index.ts +45 -1
- package/src/ink.tsx +213 -153
- package/src/{render-node-to-output.ts → paint-tree.ts} +75 -48
- package/src/reconciler.ts +24 -3
- package/src/render-background.ts +36 -15
- package/src/render-border.ts +94 -61
- package/src/render-frame.ts +83 -0
- package/src/render-to-string.ts +21 -6
- package/src/render.ts +15 -6
- 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/screen/ansi.ts +184 -0
- package/src/screen/canvas.ts +160 -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 +305 -0
- package/src/screen/serialize.ts +129 -0
- package/src/screen.ts +1 -0
- package/src/semantic-text-style.ts +118 -0
- package/src/squash-text-nodes.ts +2 -5
- package/src/structured-text.ts +325 -0
- package/src/styles.ts +19 -14
- 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.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/wrap-text.ts +4 -0
- package/dist/sgr-CMfEpjSk.d.ts +0 -91
- package/dist/truncate-Cr6xVFMa.js +0 -2330
- package/src/ansi/supports-color.ts +0 -207
- package/src/colorize.ts +0 -60
- package/src/log-update.ts +0 -370
- package/src/output.ts +0 -308
- package/src/renderer.ts +0 -73
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
import { _ as CSI, b as OSC } from "./sgr-BhwaWAJB.js";
|
|
2
|
+
import { a as isTty } from "./env-YVw64yZS.js";
|
|
3
|
+
import { c as signalExit, n as detectCapabilities } from "./detect-BuTXtY6e.js";
|
|
4
|
+
import { t as colorProfileFromLevel } from "./color-profile-DHhQHY55.js";
|
|
5
|
+
//#region src/capabilities/query.ts
|
|
6
|
+
/**
|
|
7
|
+
The DEC private modes queried via DECRQM, by name.
|
|
8
|
+
*/
|
|
9
|
+
const queriedModes = {
|
|
10
|
+
focusEvents: 1004,
|
|
11
|
+
sgrMouse: 1006,
|
|
12
|
+
sgrPixelMouse: 1016,
|
|
13
|
+
bracketedPaste: 2004,
|
|
14
|
+
synchronizedOutput: 2026,
|
|
15
|
+
graphemeClustering: 2027,
|
|
16
|
+
colorSchemeUpdates: 2031,
|
|
17
|
+
inBandResize: 2048
|
|
18
|
+
};
|
|
19
|
+
const paletteSize = 16;
|
|
20
|
+
const parseColorValue = (value) => {
|
|
21
|
+
if (value.startsWith("rgb:")) {
|
|
22
|
+
const parts = value.slice(4).split("/");
|
|
23
|
+
if (parts.length !== 3) return;
|
|
24
|
+
const channels = parts.map((part) => {
|
|
25
|
+
if (!/^[0-9a-fA-F]{1,4}$/.test(part)) return;
|
|
26
|
+
return Math.round(Number.parseInt(part, 16) / (16 ** part.length - 1) * 255);
|
|
27
|
+
});
|
|
28
|
+
if (channels.some((channel) => channel === void 0)) return;
|
|
29
|
+
return {
|
|
30
|
+
r: channels[0],
|
|
31
|
+
g: channels[1],
|
|
32
|
+
b: channels[2]
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
if (/^#[0-9a-fA-F]{6}$/.test(value)) return {
|
|
36
|
+
r: Number.parseInt(value.slice(1, 3), 16),
|
|
37
|
+
g: Number.parseInt(value.slice(3, 5), 16),
|
|
38
|
+
b: Number.parseInt(value.slice(5, 7), 16)
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
const appearanceOf = (background) => {
|
|
42
|
+
if (!background) return;
|
|
43
|
+
return .2126 * background.r + .7152 * background.g + .0722 * background.b > 127.5 ? "light" : "dark";
|
|
44
|
+
};
|
|
45
|
+
const parseXtversion = (raw) => {
|
|
46
|
+
const match = raw.match(/^(.+?)[(\s]v?(\d[\w.-]*)\)?$/);
|
|
47
|
+
if (!match) return {
|
|
48
|
+
raw,
|
|
49
|
+
name: raw.toLowerCase() || void 0,
|
|
50
|
+
version: void 0
|
|
51
|
+
};
|
|
52
|
+
return {
|
|
53
|
+
raw,
|
|
54
|
+
name: match[1].trim().toLowerCase(),
|
|
55
|
+
version: match[2]
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
const xtgettcapRgb = "524742";
|
|
59
|
+
const kittyGraphicsProbe = `_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\\`;
|
|
60
|
+
const oscColorResponse = new RegExp(`\\](\\d+);(?:(\\d+);)?([^]*)(?:|\\\\)`);
|
|
61
|
+
const kittyKeyboardResponse = new RegExp(`\\[\\?(\\d+)u`);
|
|
62
|
+
const decrqmResponse = new RegExp(`\\[\\?(\\d+);(\\d+)\\$y`);
|
|
63
|
+
const xtversionResponse = new RegExp(`P>\\|([^]*)\\\\`);
|
|
64
|
+
const xtgettcapResponse = new RegExp(`P([01])\\+r([^]*)\\\\`);
|
|
65
|
+
const kittyGraphicsResponse = new RegExp(`_G([^]*)\\\\`);
|
|
66
|
+
const winopsResponse = new RegExp(`\\[(4|6);(\\d+);(\\d+)t`);
|
|
67
|
+
const colorSchemeResponse = new RegExp(`\\[\\?997;(\\d+)n`);
|
|
68
|
+
const da1Response = new RegExp(`\\[\\?([\\d;]*)c`);
|
|
69
|
+
const buildQuery = (palette, scope) => {
|
|
70
|
+
const queries = [
|
|
71
|
+
`${OSC}10;?`,
|
|
72
|
+
`${OSC}11;?`,
|
|
73
|
+
`${OSC}12;?`
|
|
74
|
+
];
|
|
75
|
+
if (palette) for (let index = 0; index < paletteSize; index++) queries.push(`${OSC}4;${index};?`);
|
|
76
|
+
if (scope === "full") {
|
|
77
|
+
for (const mode of Object.values(queriedModes)) queries.push(`${CSI}?${mode}$p`);
|
|
78
|
+
queries.push(`${CSI}?u`, `P+q${xtgettcapRgb}\\`, kittyGraphicsProbe, `${CSI}>0q`);
|
|
79
|
+
}
|
|
80
|
+
queries.push(`${CSI}14t`, `${CSI}16t`, `${CSI}?996n`, `${CSI}c`);
|
|
81
|
+
return queries.join("");
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
Sends a batch of terminal queries and collects the responses. Resolves when
|
|
85
|
+
the terminal answers the DA1 sentinel, or after `timeout` with whatever was
|
|
86
|
+
gathered. Stdin bytes that are not query responses are pushed back into the
|
|
87
|
+
stream.
|
|
88
|
+
|
|
89
|
+
The query is lazy: nothing is sent until something asks. Inside an Ink app,
|
|
90
|
+
use `useCapabilities` — it triggers the query through Ink's input pipeline so
|
|
91
|
+
responses can't collide with key handling. Call this directly only outside of
|
|
92
|
+
Ink, before any other stdin consumer is attached.
|
|
93
|
+
*/
|
|
94
|
+
const queryTerminal = async (stdin, stdout, { timeout = 500, palette = true, scope = "full" } = {}) => new Promise((resolve) => {
|
|
95
|
+
const result = {
|
|
96
|
+
foreground: void 0,
|
|
97
|
+
background: void 0,
|
|
98
|
+
cursorColor: void 0,
|
|
99
|
+
palette: void 0,
|
|
100
|
+
appearance: void 0,
|
|
101
|
+
systemAppearance: void 0,
|
|
102
|
+
kittyKeyboard: false,
|
|
103
|
+
kittyGraphics: false,
|
|
104
|
+
sixel: false,
|
|
105
|
+
deviceAttributes: void 0,
|
|
106
|
+
trueColor: false,
|
|
107
|
+
focusEvents: false,
|
|
108
|
+
sgrMouse: false,
|
|
109
|
+
sgrPixelMouse: false,
|
|
110
|
+
bracketedPaste: false,
|
|
111
|
+
synchronizedOutput: false,
|
|
112
|
+
graphemeClustering: false,
|
|
113
|
+
colorSchemeUpdates: false,
|
|
114
|
+
inBandResize: false,
|
|
115
|
+
textAreaPixels: void 0,
|
|
116
|
+
cellPixels: void 0,
|
|
117
|
+
terminal: void 0
|
|
118
|
+
};
|
|
119
|
+
const paletteColors = /* @__PURE__ */ new Map();
|
|
120
|
+
let reportedAppearance;
|
|
121
|
+
let buffer = "";
|
|
122
|
+
let receivedStrings = false;
|
|
123
|
+
let done = false;
|
|
124
|
+
const finish = () => {
|
|
125
|
+
if (done) return;
|
|
126
|
+
done = true;
|
|
127
|
+
clearTimeout(timer);
|
|
128
|
+
stdin.removeListener("data", onData);
|
|
129
|
+
if (paletteColors.size === paletteSize) result.palette = Array.from({ length: paletteSize }, (_, index) => paletteColors.get(index));
|
|
130
|
+
result.systemAppearance = reportedAppearance;
|
|
131
|
+
result.appearance = appearanceOf(result.background) ?? reportedAppearance;
|
|
132
|
+
if (buffer.length > 0) {
|
|
133
|
+
stdin.unshift(receivedStrings ? buffer : Buffer.from(buffer, "latin1"));
|
|
134
|
+
buffer = "";
|
|
135
|
+
}
|
|
136
|
+
resolve(result);
|
|
137
|
+
};
|
|
138
|
+
const consumeMatch = (pattern) => {
|
|
139
|
+
const match = buffer.match(pattern);
|
|
140
|
+
if (match) buffer = buffer.replace(pattern, "");
|
|
141
|
+
return match ?? void 0;
|
|
142
|
+
};
|
|
143
|
+
const consumeResponse = () => {
|
|
144
|
+
const oscMatch = consumeMatch(oscColorResponse);
|
|
145
|
+
if (oscMatch) {
|
|
146
|
+
const [, code, index, value] = oscMatch;
|
|
147
|
+
const color = parseColorValue(value);
|
|
148
|
+
if (color) {
|
|
149
|
+
if (code === "10") result.foreground = color;
|
|
150
|
+
else if (code === "11") result.background = color;
|
|
151
|
+
else if (code === "12") result.cursorColor = color;
|
|
152
|
+
else if (code === "4" && index !== void 0) paletteColors.set(Number.parseInt(index, 10), color);
|
|
153
|
+
}
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
const xtversionMatch = consumeMatch(xtversionResponse);
|
|
157
|
+
if (xtversionMatch) {
|
|
158
|
+
const raw = xtversionMatch[1].trim();
|
|
159
|
+
if (raw.length > 0) result.terminal = parseXtversion(raw);
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
const xtgettcapMatch = consumeMatch(xtgettcapResponse);
|
|
163
|
+
if (xtgettcapMatch) {
|
|
164
|
+
if (xtgettcapMatch[1] === "1" && xtgettcapMatch[2].includes(xtgettcapRgb)) result.trueColor = true;
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
const graphicsMatch = consumeMatch(kittyGraphicsResponse);
|
|
168
|
+
if (graphicsMatch) {
|
|
169
|
+
if (graphicsMatch[1].includes("OK")) result.kittyGraphics = true;
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
const decrqmMatch = consumeMatch(decrqmResponse);
|
|
173
|
+
if (decrqmMatch) {
|
|
174
|
+
const mode = Number.parseInt(decrqmMatch[1], 10);
|
|
175
|
+
const recognized = decrqmMatch[2] !== "0";
|
|
176
|
+
for (const [name, number] of Object.entries(queriedModes)) if (number === mode) result[name] = recognized;
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
const winopsMatch = consumeMatch(winopsResponse);
|
|
180
|
+
if (winopsMatch) {
|
|
181
|
+
const size = {
|
|
182
|
+
height: Number.parseInt(winopsMatch[2], 10),
|
|
183
|
+
width: Number.parseInt(winopsMatch[3], 10)
|
|
184
|
+
};
|
|
185
|
+
if (winopsMatch[1] === "4") result.textAreaPixels = size;
|
|
186
|
+
else result.cellPixels = size;
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
const colorSchemeMatch = consumeMatch(colorSchemeResponse);
|
|
190
|
+
if (colorSchemeMatch) {
|
|
191
|
+
if (colorSchemeMatch[1] === "1") reportedAppearance = "dark";
|
|
192
|
+
else if (colorSchemeMatch[1] === "2") reportedAppearance = "light";
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
if (consumeMatch(kittyKeyboardResponse)) {
|
|
196
|
+
result.kittyKeyboard = true;
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
const da1Match = consumeMatch(da1Response);
|
|
200
|
+
if (da1Match) {
|
|
201
|
+
const attributes = da1Match[1].split(";").filter((part) => part.length > 0).map((part) => Number.parseInt(part, 10));
|
|
202
|
+
result.deviceAttributes = attributes;
|
|
203
|
+
result.sixel = attributes.slice(1).includes(4);
|
|
204
|
+
finish();
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
return false;
|
|
208
|
+
};
|
|
209
|
+
const onData = (data) => {
|
|
210
|
+
receivedStrings = typeof data === "string";
|
|
211
|
+
buffer += typeof data === "string" ? data : Buffer.from(data).toString("latin1");
|
|
212
|
+
while (consumeResponse());
|
|
213
|
+
};
|
|
214
|
+
stdin.on("data", onData);
|
|
215
|
+
const timer = setTimeout(finish, timeout);
|
|
216
|
+
stdout.write(buildQuery(palette, scope));
|
|
217
|
+
});
|
|
218
|
+
const queryPromises = /* @__PURE__ */ new WeakMap();
|
|
219
|
+
const queryResults = /* @__PURE__ */ new WeakMap();
|
|
220
|
+
/**
|
|
221
|
+
Starts (or joins) the terminal query for a stdout stream. The result is
|
|
222
|
+
cached per stream — the terminal is only ever asked once.
|
|
223
|
+
*/
|
|
224
|
+
const ensureTerminalQuery = (stdin, stdout, options) => {
|
|
225
|
+
let promise = queryPromises.get(stdout);
|
|
226
|
+
if (!promise) {
|
|
227
|
+
promise = queryTerminal(stdin, stdout, options);
|
|
228
|
+
promise.then((result) => queryResults.set(stdout, result));
|
|
229
|
+
queryPromises.set(stdout, promise);
|
|
230
|
+
}
|
|
231
|
+
return promise;
|
|
232
|
+
};
|
|
233
|
+
/**
|
|
234
|
+
The completed query result for a stdout stream, if the query has finished.
|
|
235
|
+
*/
|
|
236
|
+
const getTerminalQuery = (stdout) => queryResults.get(stdout);
|
|
237
|
+
const getTerminalQueryPromise = (stdout) => queryPromises.get(stdout);
|
|
238
|
+
/**
|
|
239
|
+
Merges a partial update (from an unsolicited terminal report) into the
|
|
240
|
+
cached query result. Returns the merged result, or `undefined` when no query
|
|
241
|
+
has completed yet.
|
|
242
|
+
*/
|
|
243
|
+
const patchTerminalQuery = (stdout, patch) => {
|
|
244
|
+
const previous = queryResults.get(stdout);
|
|
245
|
+
if (!previous) return;
|
|
246
|
+
const merged = {
|
|
247
|
+
...previous,
|
|
248
|
+
...patch
|
|
249
|
+
};
|
|
250
|
+
queryResults.set(stdout, merged);
|
|
251
|
+
queryPromises.set(stdout, Promise.resolve(merged));
|
|
252
|
+
return merged;
|
|
253
|
+
};
|
|
254
|
+
const refreshPromises = /* @__PURE__ */ new WeakMap();
|
|
255
|
+
/**
|
|
256
|
+
Re-asks the terminal only the dynamic questions — colors/theme and pixel
|
|
257
|
+
geometry, which change when the user switches themes or resizes — and merges
|
|
258
|
+
the answers into the cached result. Static facts (mode and protocol support,
|
|
259
|
+
identity) are kept from the original query. Falls back to a full query when
|
|
260
|
+
none has completed yet; concurrent refreshes share one round-trip.
|
|
261
|
+
*/
|
|
262
|
+
const refreshTerminalQuery = (stdin, stdout, options) => {
|
|
263
|
+
const previous = queryResults.get(stdout);
|
|
264
|
+
if (!previous) return ensureTerminalQuery(stdin, stdout, options);
|
|
265
|
+
const inFlight = refreshPromises.get(stdout);
|
|
266
|
+
if (inFlight) return inFlight;
|
|
267
|
+
const promise = queryTerminal(stdin, stdout, {
|
|
268
|
+
...options,
|
|
269
|
+
scope: "dynamic"
|
|
270
|
+
}).then((fresh) => {
|
|
271
|
+
const merged = {
|
|
272
|
+
...previous,
|
|
273
|
+
foreground: fresh.foreground ?? previous.foreground,
|
|
274
|
+
background: fresh.background ?? previous.background,
|
|
275
|
+
cursorColor: fresh.cursorColor ?? previous.cursorColor,
|
|
276
|
+
palette: fresh.palette ?? previous.palette,
|
|
277
|
+
appearance: fresh.appearance ?? previous.appearance,
|
|
278
|
+
systemAppearance: fresh.systemAppearance ?? previous.systemAppearance,
|
|
279
|
+
textAreaPixels: fresh.textAreaPixels ?? previous.textAreaPixels,
|
|
280
|
+
cellPixels: fresh.cellPixels ?? previous.cellPixels
|
|
281
|
+
};
|
|
282
|
+
queryResults.set(stdout, merged);
|
|
283
|
+
queryPromises.set(stdout, Promise.resolve(merged));
|
|
284
|
+
refreshPromises.delete(stdout);
|
|
285
|
+
return merged;
|
|
286
|
+
});
|
|
287
|
+
refreshPromises.set(stdout, promise);
|
|
288
|
+
return promise;
|
|
289
|
+
};
|
|
290
|
+
/**
|
|
291
|
+
Merges an async query result into a synchronous capabilities snapshot,
|
|
292
|
+
producing the complete picture. The query is authoritative where it answered:
|
|
293
|
+
a terminal that confirms truecolor via XTGETTCAP upgrades the sniffed color
|
|
294
|
+
level.
|
|
295
|
+
*/
|
|
296
|
+
const applyTerminalQuery = (capabilities, query) => ({
|
|
297
|
+
...capabilities,
|
|
298
|
+
size: {
|
|
299
|
+
...capabilities.size,
|
|
300
|
+
pixels: query.textAreaPixels || query.cellPixels ? {
|
|
301
|
+
textArea: query.textAreaPixels,
|
|
302
|
+
cell: query.cellPixels
|
|
303
|
+
} : void 0
|
|
304
|
+
},
|
|
305
|
+
terminal: {
|
|
306
|
+
...capabilities.terminal,
|
|
307
|
+
name: query.terminal?.name ?? capabilities.terminal.name,
|
|
308
|
+
version: query.terminal?.version ?? capabilities.terminal.version
|
|
309
|
+
},
|
|
310
|
+
color: query.trueColor ? {
|
|
311
|
+
level: 3,
|
|
312
|
+
depth: 24,
|
|
313
|
+
trueColor: true
|
|
314
|
+
} : capabilities.color,
|
|
315
|
+
theme: {
|
|
316
|
+
appearance: query.appearance ?? capabilities.theme.appearance,
|
|
317
|
+
systemAppearance: query.systemAppearance,
|
|
318
|
+
foreground: query.foreground,
|
|
319
|
+
background: query.background,
|
|
320
|
+
cursor: query.cursorColor,
|
|
321
|
+
palette: query.palette
|
|
322
|
+
},
|
|
323
|
+
supports: {
|
|
324
|
+
...capabilities.supports,
|
|
325
|
+
color: capabilities.supports.color || query.trueColor,
|
|
326
|
+
kittyKeyboard: query.kittyKeyboard,
|
|
327
|
+
kittyGraphics: query.kittyGraphics,
|
|
328
|
+
sixel: query.sixel,
|
|
329
|
+
focusEvents: query.focusEvents,
|
|
330
|
+
sgrMouse: query.sgrMouse,
|
|
331
|
+
sgrPixelMouse: query.sgrPixelMouse,
|
|
332
|
+
bracketedPaste: query.bracketedPaste,
|
|
333
|
+
synchronizedOutput: query.synchronizedOutput,
|
|
334
|
+
graphemeClustering: query.graphemeClustering,
|
|
335
|
+
colorSchemeUpdates: query.colorSchemeUpdates,
|
|
336
|
+
inBandResize: query.inBandResize
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
//#endregion
|
|
340
|
+
//#region src/stream.ts
|
|
341
|
+
const isRawModeStream = (stdin) => {
|
|
342
|
+
return isTty(stdin) && "setRawMode" in stdin && typeof stdin.setRawMode === "function";
|
|
343
|
+
};
|
|
344
|
+
const getRawModeStream = (stdin) => {
|
|
345
|
+
if (!isRawModeStream(stdin)) return;
|
|
346
|
+
return stdin;
|
|
347
|
+
};
|
|
348
|
+
//#endregion
|
|
349
|
+
//#region src/capabilities/store.ts
|
|
350
|
+
const integrations = /* @__PURE__ */ new WeakMap();
|
|
351
|
+
const reportingUpdaters = /* @__PURE__ */ new WeakMap();
|
|
352
|
+
const registerTerminalIntegration = (stdout, integration) => {
|
|
353
|
+
integrations.set(stdout, integration);
|
|
354
|
+
reportingUpdaters.get(stdout)?.();
|
|
355
|
+
return () => {
|
|
356
|
+
if (integrations.get(stdout) === integration) {
|
|
357
|
+
integrations.delete(stdout);
|
|
358
|
+
reportingUpdaters.get(stdout)?.();
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
};
|
|
362
|
+
const focusInReport = `${CSI}I`;
|
|
363
|
+
const focusOutReport = `${CSI}O`;
|
|
364
|
+
const colorSchemeReport = new RegExp(`^\\[\\?997;(\\d+)n$`);
|
|
365
|
+
const inBandResizeReport = new RegExp(`^\\[48;(\\d+);(\\d+);(\\d+);(\\d+)t$`);
|
|
366
|
+
const reportModes = [
|
|
367
|
+
[1004, (result) => result.focusEvents],
|
|
368
|
+
[2031, (result) => result.colorSchemeUpdates],
|
|
369
|
+
[2048, (result) => result.inBandResize]
|
|
370
|
+
];
|
|
371
|
+
const stores = /* @__PURE__ */ new WeakMap();
|
|
372
|
+
const createStore = (stdin, stdout) => {
|
|
373
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
374
|
+
let resizeSubscribers = 0;
|
|
375
|
+
let focused;
|
|
376
|
+
let snapshot;
|
|
377
|
+
let snapshotQuery;
|
|
378
|
+
let snapshotColumns;
|
|
379
|
+
let snapshotRows;
|
|
380
|
+
let snapshotFocused;
|
|
381
|
+
const current = () => {
|
|
382
|
+
const query = getTerminalQuery(stdout);
|
|
383
|
+
const { columns, rows } = stdout;
|
|
384
|
+
if (!snapshot || query !== snapshotQuery || columns !== snapshotColumns || rows !== snapshotRows || focused !== snapshotFocused) {
|
|
385
|
+
const detected = detectCapabilities({ stdout });
|
|
386
|
+
const applied = query ? applyTerminalQuery(detected, query) : detected;
|
|
387
|
+
snapshot = focused === void 0 ? applied : {
|
|
388
|
+
...applied,
|
|
389
|
+
focused
|
|
390
|
+
};
|
|
391
|
+
snapshotQuery = query;
|
|
392
|
+
snapshotColumns = columns;
|
|
393
|
+
snapshotRows = rows;
|
|
394
|
+
snapshotFocused = focused;
|
|
395
|
+
}
|
|
396
|
+
return snapshot;
|
|
397
|
+
};
|
|
398
|
+
let lastNotified;
|
|
399
|
+
const notify = () => {
|
|
400
|
+
const capabilities = current();
|
|
401
|
+
if (capabilities === lastNotified) return;
|
|
402
|
+
lastNotified = capabilities;
|
|
403
|
+
for (const listener of listeners) listener(capabilities);
|
|
404
|
+
};
|
|
405
|
+
let reportingEnabled = false;
|
|
406
|
+
let removeExitHandler;
|
|
407
|
+
const enabledModes = () => {
|
|
408
|
+
const result = getTerminalQuery(stdout);
|
|
409
|
+
if (!result) return [];
|
|
410
|
+
return reportModes.filter(([, supported]) => supported(result)).map(([mode]) => mode);
|
|
411
|
+
};
|
|
412
|
+
const writeModes = (suffix) => {
|
|
413
|
+
const sequence = enabledModes().map((mode) => `${CSI}?${mode}${suffix}`).join("");
|
|
414
|
+
if (sequence.length > 0) try {
|
|
415
|
+
stdout.write(sequence);
|
|
416
|
+
} catch {}
|
|
417
|
+
};
|
|
418
|
+
const updateReporting = () => {
|
|
419
|
+
const integration = integrations.get(stdout);
|
|
420
|
+
const shouldEnable = listeners.size > 0 && integration !== void 0 && stdout.isTTY === true;
|
|
421
|
+
if (shouldEnable && !reportingEnabled && enabledModes().length > 0) {
|
|
422
|
+
reportingEnabled = true;
|
|
423
|
+
integration?.setReportFeed?.(true);
|
|
424
|
+
writeModes("h");
|
|
425
|
+
removeExitHandler = signalExit(() => {
|
|
426
|
+
writeModes("l");
|
|
427
|
+
});
|
|
428
|
+
} else if (!shouldEnable && reportingEnabled) {
|
|
429
|
+
reportingEnabled = false;
|
|
430
|
+
writeModes("l");
|
|
431
|
+
integration?.setReportFeed?.(false);
|
|
432
|
+
removeExitHandler?.();
|
|
433
|
+
removeExitHandler = void 0;
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
reportingUpdaters.set(stdout, updateReporting);
|
|
437
|
+
const ingest = (sequence) => {
|
|
438
|
+
if (sequence === focusInReport || sequence === focusOutReport) {
|
|
439
|
+
focused = sequence === focusInReport;
|
|
440
|
+
notify();
|
|
441
|
+
return true;
|
|
442
|
+
}
|
|
443
|
+
const colorScheme = sequence.match(colorSchemeReport);
|
|
444
|
+
if (colorScheme) {
|
|
445
|
+
const reported = colorScheme[1] === "1" ? "dark" : colorScheme[1] === "2" ? "light" : void 0;
|
|
446
|
+
if (reported) {
|
|
447
|
+
const cached = getTerminalQuery(stdout);
|
|
448
|
+
patchTerminalQuery(stdout, {
|
|
449
|
+
systemAppearance: reported,
|
|
450
|
+
...cached?.background === void 0 ? { appearance: reported } : {}
|
|
451
|
+
});
|
|
452
|
+
notify();
|
|
453
|
+
if (integrations.has(stdout)) query().catch(() => {});
|
|
454
|
+
}
|
|
455
|
+
return true;
|
|
456
|
+
}
|
|
457
|
+
const resize = sequence.match(inBandResizeReport);
|
|
458
|
+
if (resize) {
|
|
459
|
+
const [, rows, columns, pixelHeight, pixelWidth] = resize.map(Number);
|
|
460
|
+
if (columns && rows) patchTerminalQuery(stdout, {
|
|
461
|
+
textAreaPixels: pixelWidth && pixelHeight ? {
|
|
462
|
+
width: pixelWidth,
|
|
463
|
+
height: pixelHeight
|
|
464
|
+
} : void 0,
|
|
465
|
+
cellPixels: pixelWidth && pixelHeight ? {
|
|
466
|
+
width: Math.round(pixelWidth / columns),
|
|
467
|
+
height: Math.round(pixelHeight / rows)
|
|
468
|
+
} : void 0
|
|
469
|
+
});
|
|
470
|
+
notify();
|
|
471
|
+
return true;
|
|
472
|
+
}
|
|
473
|
+
return false;
|
|
474
|
+
};
|
|
475
|
+
const runStandaloneQuery = async () => {
|
|
476
|
+
if (!stdout.isTTY || !stdin.isTTY) return;
|
|
477
|
+
const wasRaw = stdin.isRaw ?? false;
|
|
478
|
+
if (!wasRaw) stdin.setRawMode?.(true);
|
|
479
|
+
try {
|
|
480
|
+
await (getTerminalQuery(stdout) !== void 0 ? refreshTerminalQuery(stdin, stdout) : ensureTerminalQuery(stdin, stdout));
|
|
481
|
+
} finally {
|
|
482
|
+
if (!wasRaw) stdin.setRawMode?.(false);
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
const query = async () => {
|
|
486
|
+
const integration = integrations.get(stdout);
|
|
487
|
+
if (integration) await integration.runQuery({ refresh: getTerminalQuery(stdout) !== void 0 });
|
|
488
|
+
else await runStandaloneQuery();
|
|
489
|
+
notify();
|
|
490
|
+
updateReporting();
|
|
491
|
+
return current();
|
|
492
|
+
};
|
|
493
|
+
const onResize = () => {
|
|
494
|
+
notify();
|
|
495
|
+
};
|
|
496
|
+
const subscribe = (listener, options = {}) => {
|
|
497
|
+
const resizes = options.resizes ?? true;
|
|
498
|
+
if (resizes && resizeSubscribers++ === 0) stdout.on("resize", onResize);
|
|
499
|
+
listeners.add(listener);
|
|
500
|
+
updateReporting();
|
|
501
|
+
let subscribed = true;
|
|
502
|
+
return () => {
|
|
503
|
+
if (!subscribed) return;
|
|
504
|
+
subscribed = false;
|
|
505
|
+
listeners.delete(listener);
|
|
506
|
+
if (resizes && --resizeSubscribers === 0) stdout.removeListener("resize", onResize);
|
|
507
|
+
updateReporting();
|
|
508
|
+
};
|
|
509
|
+
};
|
|
510
|
+
return {
|
|
511
|
+
get current() {
|
|
512
|
+
return current();
|
|
513
|
+
},
|
|
514
|
+
query,
|
|
515
|
+
subscribe,
|
|
516
|
+
ingest
|
|
517
|
+
};
|
|
518
|
+
};
|
|
519
|
+
/**
|
|
520
|
+
The capabilities store for a stream pair. Stores are cached per stdout, so
|
|
521
|
+
standalone code and Ink components observing the same terminal share one
|
|
522
|
+
snapshot and one set of query answers.
|
|
523
|
+
*/
|
|
524
|
+
const getCapabilities = (stdin = process.stdin, stdout = process.stdout) => {
|
|
525
|
+
let store = stores.get(stdout);
|
|
526
|
+
if (!store) {
|
|
527
|
+
store = createStore(stdin, stdout);
|
|
528
|
+
stores.set(stdout, store);
|
|
529
|
+
}
|
|
530
|
+
return store;
|
|
531
|
+
};
|
|
532
|
+
/**
|
|
533
|
+
The process's own terminal — the store for `process.stdin`/`process.stdout`.
|
|
534
|
+
|
|
535
|
+
```ts
|
|
536
|
+
import { capabilities } from "@alchemy.run/sigil/capabilities";
|
|
537
|
+
|
|
538
|
+
capabilities.current.supports.hyperlinks;
|
|
539
|
+
const fresh = await capabilities.query();
|
|
540
|
+
const unsubscribe = capabilities.subscribe((caps) => {
|
|
541
|
+
console.log(caps.size, caps.theme.appearance);
|
|
542
|
+
});
|
|
543
|
+
```
|
|
544
|
+
*/
|
|
545
|
+
const capabilities = getCapabilities();
|
|
546
|
+
//#endregion
|
|
547
|
+
//#region src/capabilities/color-policy.ts
|
|
548
|
+
function resolveColorProfile(detectedLevel, policy = "auto") {
|
|
549
|
+
return policy === "auto" ? colorProfileFromLevel(detectedLevel) : policy;
|
|
550
|
+
}
|
|
551
|
+
function colorState(capabilities, policy = "auto") {
|
|
552
|
+
const detected = colorProfileFromLevel(capabilities.color.level);
|
|
553
|
+
return {
|
|
554
|
+
detected,
|
|
555
|
+
policy,
|
|
556
|
+
effective: policy === "auto" ? detected : policy
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
//#endregion
|
|
560
|
+
export { registerTerminalIntegration as a, ensureTerminalQuery as c, queryTerminal as d, refreshTerminalQuery as f, getCapabilities as i, getTerminalQuery as l, resolveColorProfile as n, getRawModeStream as o, capabilities as r, applyTerminalQuery as s, colorState as t, getTerminalQueryPromise as u };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { d as rgbToAnsi, f as rgbToAnsi256, t as ansi256ToAnsi } from "./sgr-BhwaWAJB.js";
|
|
2
|
+
//#region src/screen/color-profile.ts
|
|
3
|
+
function colorProfileFromLevel(level) {
|
|
4
|
+
return [
|
|
5
|
+
"none",
|
|
6
|
+
"ansi16",
|
|
7
|
+
"ansi256",
|
|
8
|
+
"truecolor"
|
|
9
|
+
][level];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
Quantizes a semantic color for an output profile. `undefined` means the
|
|
13
|
+
terminal default and is also the result for the no-color profile.
|
|
14
|
+
*/
|
|
15
|
+
function quantizeColor(color, profile) {
|
|
16
|
+
if (!color || profile === "none") return;
|
|
17
|
+
if (profile === "truecolor") return color;
|
|
18
|
+
if (profile === "ansi256") {
|
|
19
|
+
if (color.model === "indexed") return color;
|
|
20
|
+
return {
|
|
21
|
+
model: "indexed",
|
|
22
|
+
index: rgbToAnsi256(color.red, color.green, color.blue),
|
|
23
|
+
encoding: "ansi256"
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
model: "indexed",
|
|
28
|
+
index: ansiCodeToPaletteIndex(color.model === "indexed" ? ansi256ToAnsi(color.index) : rgbToAnsi(color.red, color.green, color.blue)),
|
|
29
|
+
encoding: "ansi16"
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function ansiCodeToPaletteIndex(code) {
|
|
33
|
+
return code >= 90 ? code - 90 + 8 : code - 30;
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
36
|
+
export { quantizeColor as n, colorProfileFromLevel as t };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { i as ColorSupportLevel } from "./detect-Bh4yGP6w.js";
|
|
2
|
+
//#region src/screen/cell.d.ts
|
|
3
|
+
/**
|
|
4
|
+
A terminal color before it is converted to an output profile.
|
|
5
|
+
*/
|
|
6
|
+
type Color = RgbColor | IndexedColor;
|
|
7
|
+
type RgbColor = {
|
|
8
|
+
readonly model: "rgb";
|
|
9
|
+
readonly red: number;
|
|
10
|
+
readonly green: number;
|
|
11
|
+
readonly blue: number;
|
|
12
|
+
readonly alpha: number;
|
|
13
|
+
};
|
|
14
|
+
type IndexedColor = {
|
|
15
|
+
readonly model: "indexed";
|
|
16
|
+
readonly index: number;
|
|
17
|
+
/** Preserve an explicit palette encoding when compatibility requires it. */
|
|
18
|
+
readonly encoding?: "ansi16" | "ansi256";
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
Bit flags for terminal text attributes. Multiple attributes may be combined.
|
|
22
|
+
*/
|
|
23
|
+
declare const cellAttributes: {
|
|
24
|
+
readonly none: 0;
|
|
25
|
+
readonly bold: number;
|
|
26
|
+
readonly faint: number;
|
|
27
|
+
readonly italic: number;
|
|
28
|
+
readonly blink: number;
|
|
29
|
+
readonly rapidBlink: number;
|
|
30
|
+
readonly inverse: number;
|
|
31
|
+
readonly hidden: number;
|
|
32
|
+
readonly strikethrough: number;
|
|
33
|
+
};
|
|
34
|
+
type UnderlineStyle = "none" | "single" | "double" | "curly" | "dotted" | "dashed";
|
|
35
|
+
type CellStyle = {
|
|
36
|
+
readonly foreground?: Color;
|
|
37
|
+
readonly background?: Color;
|
|
38
|
+
readonly underlineColor?: Color;
|
|
39
|
+
readonly underline: UnderlineStyle;
|
|
40
|
+
readonly attributes: number;
|
|
41
|
+
};
|
|
42
|
+
type Hyperlink = {
|
|
43
|
+
readonly url: string;
|
|
44
|
+
readonly parameters?: string;
|
|
45
|
+
};
|
|
46
|
+
type CellContent = {
|
|
47
|
+
readonly grapheme: string;
|
|
48
|
+
readonly width: number;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
A compositing operation for one screen position.
|
|
52
|
+
|
|
53
|
+
An absent field is transparent and preserves the destination channel. `null`
|
|
54
|
+
explicitly resets a color or hyperlink to the terminal default. Supplying
|
|
55
|
+
`content` paints a grapheme; `{grapheme: " ", width: 1}` is therefore an
|
|
56
|
+
explicit blank, while an absent patch paints nothing at all.
|
|
57
|
+
*/
|
|
58
|
+
type CellPatch = {
|
|
59
|
+
readonly content?: CellContent;
|
|
60
|
+
readonly foreground?: Color | null;
|
|
61
|
+
readonly background?: Color | null;
|
|
62
|
+
readonly underlineColor?: Color | null;
|
|
63
|
+
readonly underline?: UnderlineStyle;
|
|
64
|
+
readonly attributes?: number;
|
|
65
|
+
readonly hyperlink?: Hyperlink | null;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
One grapheme cluster in a terminal screen. A width of zero is reserved for the
|
|
69
|
+
continuation columns of a wide grapheme and is created only by `Screen`.
|
|
70
|
+
*/
|
|
71
|
+
type Cell = {
|
|
72
|
+
readonly grapheme: string;
|
|
73
|
+
readonly width: number;
|
|
74
|
+
readonly style: CellStyle;
|
|
75
|
+
readonly hyperlink?: Hyperlink;
|
|
76
|
+
/** Composition intent consumed when this cell is drawn over another surface. */
|
|
77
|
+
readonly reset?: {
|
|
78
|
+
readonly foreground?: boolean;
|
|
79
|
+
readonly background?: boolean;
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
declare const emptyCellStyle: CellStyle;
|
|
83
|
+
declare const emptyCell: Cell;
|
|
84
|
+
declare const createCell: (grapheme: string, width: number, style?: CellStyle, hyperlink?: Hyperlink, reset?: Cell["reset"]) => Cell;
|
|
85
|
+
declare const cellsEqual: (left: Cell | undefined, right: Cell | undefined) => boolean;
|
|
86
|
+
//#endregion
|
|
87
|
+
//#region src/screen/color-profile.d.ts
|
|
88
|
+
/** The color model emitted by a terminal serializer. */
|
|
89
|
+
type ColorProfile = "none" | "ansi16" | "ansi256" | "truecolor";
|
|
90
|
+
declare function colorProfileFromLevel(level: ColorSupportLevel): ColorProfile;
|
|
91
|
+
/**
|
|
92
|
+
Quantizes a semantic color for an output profile. `undefined` means the
|
|
93
|
+
terminal default and is also the result for the no-color profile.
|
|
94
|
+
*/
|
|
95
|
+
declare function quantizeColor(color: Color | undefined, profile: ColorProfile): Color | undefined;
|
|
96
|
+
//#endregion
|
|
97
|
+
export { emptyCellStyle as _, CellContent as a, Color as c, RgbColor as d, UnderlineStyle as f, emptyCell as g, createCell as h, Cell as i, Hyperlink as l, cellsEqual as m, colorProfileFromLevel as n, CellPatch as o, cellAttributes as p, quantizeColor as r, CellStyle as s, ColorProfile as t, IndexedColor as u };
|