tinky-termcap 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.ja-JP.md +285 -0
- package/README.md +287 -0
- package/README.zh-CN.md +283 -0
- package/lib/TermcapContext.d.ts +62 -0
- package/lib/TermcapContext.d.ts.map +1 -0
- package/lib/TermcapContext.js +103 -0
- package/lib/TermcapContext.js.map +1 -0
- package/lib/contexts/TermcapContext.d.ts +237 -0
- package/lib/contexts/TermcapContext.js +157 -0
- package/lib/detect.d.ts +38 -0
- package/lib/detect.d.ts.map +1 -0
- package/lib/detect.js +243 -0
- package/lib/detect.js.map +1 -0
- package/lib/hooks/use-termcap.d.ts +151 -0
- package/lib/hooks/use-termcap.js +161 -0
- package/lib/index.d.ts +85 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +92 -0
- package/lib/index.js.map +1 -0
- package/lib/utils/detect-termcap.d.ts +328 -0
- package/lib/utils/detect-termcap.js +365 -0
- package/lib/utils/detect.d.ts +33 -0
- package/lib/utils/detect.js +233 -0
- package/lib/utils/term-features.d.ts +242 -0
- package/lib/utils/term-features.js +208 -0
- package/package.json +73 -0
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview Terminal capability detection utilities.
|
|
4
|
+
*
|
|
5
|
+
* This module provides functions and types for detecting terminal capabilities
|
|
6
|
+
* by sending escape sequence queries to the terminal and parsing responses.
|
|
7
|
+
* It supports detection of:
|
|
8
|
+
* - Background color (via OSC 11)
|
|
9
|
+
* - Terminal name/version (via XTVERSION)
|
|
10
|
+
* - Kitty keyboard protocol support
|
|
11
|
+
* - modifyOtherKeys mode support
|
|
12
|
+
*
|
|
13
|
+
* @example Basic usage with tinky
|
|
14
|
+
* ```tsx
|
|
15
|
+
* import { TermcapProvider, useTermcap } from "tinky-termcap";
|
|
16
|
+
*
|
|
17
|
+
* function App() {
|
|
18
|
+
* const { isReady, backgroundColor, kittyProtocol } = useTermcap();
|
|
19
|
+
*
|
|
20
|
+
* if (!isReady) return <Text>Detecting...</Text>;
|
|
21
|
+
*
|
|
22
|
+
* return (
|
|
23
|
+
* <Box>
|
|
24
|
+
* <Text>Background: {backgroundColor ?? "unknown"}</Text>
|
|
25
|
+
* <Text>Kitty: {kittyProtocol ? "yes" : "no"}</Text>
|
|
26
|
+
* </Box>
|
|
27
|
+
* );
|
|
28
|
+
* }
|
|
29
|
+
*
|
|
30
|
+
* render(
|
|
31
|
+
* <TermcapProvider>
|
|
32
|
+
* <App />
|
|
33
|
+
* </TermcapProvider>
|
|
34
|
+
* );
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @example Direct usage without React
|
|
38
|
+
* ```typescript
|
|
39
|
+
* import { detectTermcap } from "tinky-termcap";
|
|
40
|
+
*
|
|
41
|
+
* async function main() {
|
|
42
|
+
* const caps = await detectTermcap(process.stdin, process.stdout, 1000);
|
|
43
|
+
* console.log("Terminal capabilities:", caps);
|
|
44
|
+
* }
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* @packageDocumentation
|
|
48
|
+
*/
|
|
49
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
50
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
51
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
52
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
53
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
54
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
55
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
59
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
60
|
+
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
61
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
62
|
+
function step(op) {
|
|
63
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
64
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
65
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
66
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
67
|
+
switch (op[0]) {
|
|
68
|
+
case 0: case 1: t = op; break;
|
|
69
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
70
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
71
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
72
|
+
default:
|
|
73
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
74
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
75
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
76
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
77
|
+
if (t[2]) _.ops.pop();
|
|
78
|
+
_.trys.pop(); continue;
|
|
79
|
+
}
|
|
80
|
+
op = body.call(thisArg, _);
|
|
81
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
82
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
86
|
+
exports.DEFAULT_DETECTION_TIMEOUT = void 0;
|
|
87
|
+
exports.detectTermcap = detectTermcap;
|
|
88
|
+
exports.resetForTesting = resetForTesting;
|
|
89
|
+
var term_features_js_1 = require("./term-features.js");
|
|
90
|
+
/**
|
|
91
|
+
* Parse RGB color from hex components to #rrggbb format.
|
|
92
|
+
*
|
|
93
|
+
* Terminal color responses use variable-length hex values (1-4 digits per
|
|
94
|
+
* component). This function normalizes them to the standard `#rrggbb` format.
|
|
95
|
+
*
|
|
96
|
+
* @param rHex - Red component in hex (1-4 digits)
|
|
97
|
+
* @param gHex - Green component in hex (1-4 digits)
|
|
98
|
+
* @param bHex - Blue component in hex (1-4 digits)
|
|
99
|
+
* @returns Normalized color string in `#rrggbb` format
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```typescript
|
|
103
|
+
* // 2-digit hex (most common)
|
|
104
|
+
* parseColor("1a", "1a", "1a"); // "#1a1a1a"
|
|
105
|
+
*
|
|
106
|
+
* // 4-digit hex (some terminals)
|
|
107
|
+
* parseColor("1a1a", "1a1a", "1a1a"); // "#1a1a1a"
|
|
108
|
+
*
|
|
109
|
+
* // 1-digit hex
|
|
110
|
+
* parseColor("f", "0", "0"); // "#ff0000"
|
|
111
|
+
* ```
|
|
112
|
+
*
|
|
113
|
+
* @internal
|
|
114
|
+
*/
|
|
115
|
+
function parseColor(rHex, gHex, bHex) {
|
|
116
|
+
var parseComponent = function (hex) {
|
|
117
|
+
var val = parseInt(hex, 16);
|
|
118
|
+
if (hex.length === 1)
|
|
119
|
+
return (val / 15) * 255;
|
|
120
|
+
if (hex.length === 2)
|
|
121
|
+
return val;
|
|
122
|
+
if (hex.length === 3)
|
|
123
|
+
return (val / 4095) * 255;
|
|
124
|
+
if (hex.length === 4)
|
|
125
|
+
return (val / 65535) * 255;
|
|
126
|
+
return val;
|
|
127
|
+
};
|
|
128
|
+
var r = parseComponent(rHex);
|
|
129
|
+
var g = parseComponent(gHex);
|
|
130
|
+
var b = parseComponent(bHex);
|
|
131
|
+
var toHex = function (c) { return Math.round(c).toString(16).padStart(2, "0"); };
|
|
132
|
+
return "#".concat(toHex(r)).concat(toHex(g)).concat(toHex(b));
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Default timeout for capability detection in milliseconds.
|
|
136
|
+
*
|
|
137
|
+
* This value (1000ms) provides a reasonable balance between:
|
|
138
|
+
* - Allowing enough time for slow terminals to respond
|
|
139
|
+
* - Not delaying application startup too long if terminal doesn't respond
|
|
140
|
+
*
|
|
141
|
+
* @example Custom timeout usage
|
|
142
|
+
* ```typescript
|
|
143
|
+
* import { detectTermcap, DEFAULT_DETECTION_TIMEOUT } from "tinky-termcap";
|
|
144
|
+
*
|
|
145
|
+
* // Use half the default timeout for faster startup
|
|
146
|
+
* const caps = await detectTermcap(stdin, stdout, DEFAULT_DETECTION_TIMEOUT / 2);
|
|
147
|
+
*
|
|
148
|
+
* // Or use a longer timeout for slow connections
|
|
149
|
+
* const caps = await detectTermcap(stdin, stdout, DEFAULT_DETECTION_TIMEOUT * 2);
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
152
|
+
exports.DEFAULT_DETECTION_TIMEOUT = 1000;
|
|
153
|
+
/**
|
|
154
|
+
* Detect terminal capabilities by querying the terminal.
|
|
155
|
+
*
|
|
156
|
+
* This function sends escape sequence queries to the terminal and parses
|
|
157
|
+
* the responses to determine supported features. It detects:
|
|
158
|
+
* - **Background color** - via OSC 11 query
|
|
159
|
+
* - **Terminal name** - via XTVERSION query
|
|
160
|
+
* - **Kitty protocol** - enhanced keyboard support
|
|
161
|
+
* - **modifyOtherKeys** - key disambiguation support
|
|
162
|
+
*
|
|
163
|
+
* The function uses Device Attributes (DA) as a "sentinel" - when the DA
|
|
164
|
+
* response is received, detection is considered complete since terminals
|
|
165
|
+
* always respond to DA queries.
|
|
166
|
+
*
|
|
167
|
+
* @param stdin - Input stream to read terminal responses from.
|
|
168
|
+
* If not provided or not a TTY, returns default values immediately.
|
|
169
|
+
* @param stdout - Output stream to write queries to.
|
|
170
|
+
* If not provided or not a TTY, returns default values immediately.
|
|
171
|
+
* @param timeout - Maximum time to wait for responses in milliseconds.
|
|
172
|
+
* Defaults to {@link DEFAULT_DETECTION_TIMEOUT} (1000ms).
|
|
173
|
+
* @returns Promise resolving to detected capabilities.
|
|
174
|
+
*
|
|
175
|
+
* @remarks
|
|
176
|
+
* - This function should typically only be called once at app startup
|
|
177
|
+
* - The caller is responsible for enabling raw mode before calling
|
|
178
|
+
* - In non-TTY environments, returns immediately with default values
|
|
179
|
+
*
|
|
180
|
+
* @example Basic usage
|
|
181
|
+
* ```typescript
|
|
182
|
+
* import { detectTermcap } from "tinky-termcap";
|
|
183
|
+
*
|
|
184
|
+
* async function main() {
|
|
185
|
+
* // Enable raw mode first (required for reading responses)
|
|
186
|
+
* process.stdin.setRawMode(true);
|
|
187
|
+
*
|
|
188
|
+
* const caps = await detectTermcap(
|
|
189
|
+
* process.stdin,
|
|
190
|
+
* process.stdout,
|
|
191
|
+
* 1000
|
|
192
|
+
* );
|
|
193
|
+
*
|
|
194
|
+
* console.log("Detection complete:");
|
|
195
|
+
* console.log(" Background:", caps.backgroundColor ?? "unknown");
|
|
196
|
+
* console.log(" Terminal:", caps.terminalName ?? "unknown");
|
|
197
|
+
* console.log(" Kitty:", caps.kittyProtocol ? "yes" : "no");
|
|
198
|
+
* console.log(" modifyOtherKeys:", caps.modifyOtherKeys ? "yes" : "no");
|
|
199
|
+
* }
|
|
200
|
+
* ```
|
|
201
|
+
*
|
|
202
|
+
* @example With tinky hooks
|
|
203
|
+
* ```tsx
|
|
204
|
+
* import { useStdin, useStdout } from "tinky";
|
|
205
|
+
* import { detectTermcap } from "tinky-termcap";
|
|
206
|
+
*
|
|
207
|
+
* function DetectionComponent() {
|
|
208
|
+
* const { stdin, setRawMode } = useStdin();
|
|
209
|
+
* const { stdout } = useStdout();
|
|
210
|
+
* const [caps, setCaps] = useState<TermcapInfo | null>(null);
|
|
211
|
+
*
|
|
212
|
+
* useEffect(() => {
|
|
213
|
+
* setRawMode(true);
|
|
214
|
+
* detectTermcap(stdin, stdout, 1000).then(setCaps);
|
|
215
|
+
* }, []);
|
|
216
|
+
*
|
|
217
|
+
* if (!caps) return <Text>Detecting...</Text>;
|
|
218
|
+
* return <Text>Ready!</Text>;
|
|
219
|
+
* }
|
|
220
|
+
* ```
|
|
221
|
+
*
|
|
222
|
+
* @example Testing with mock streams
|
|
223
|
+
* ```typescript
|
|
224
|
+
* import { EventEmitter } from "events";
|
|
225
|
+
* import { ReadStream, WriteStream } from "tinky";
|
|
226
|
+
* import { detectTermcap } from "tinky-termcap";
|
|
227
|
+
*
|
|
228
|
+
* // Create mock streams
|
|
229
|
+
* const mockStdin = new EventEmitter() as ReadStream;
|
|
230
|
+
* (mockStdin as any).isTTY = true;
|
|
231
|
+
*
|
|
232
|
+
* const mockStdout: WriteStream = {
|
|
233
|
+
* isTTY: true,
|
|
234
|
+
* write(data: string) {
|
|
235
|
+
* // Simulate terminal responding to queries
|
|
236
|
+
* setTimeout(() => {
|
|
237
|
+
* mockStdin.emit("data", Buffer.from("\x1b[?62;c")); // DA response
|
|
238
|
+
* }, 10);
|
|
239
|
+
* return true;
|
|
240
|
+
* },
|
|
241
|
+
* };
|
|
242
|
+
*
|
|
243
|
+
* const caps = await detectTermcap(mockStdin, mockStdout, 100);
|
|
244
|
+
* ```
|
|
245
|
+
*/
|
|
246
|
+
function detectTermcap(stdin, stdout, timeout) {
|
|
247
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
248
|
+
var defaultResult;
|
|
249
|
+
return __generator(this, function (_a) {
|
|
250
|
+
defaultResult = {
|
|
251
|
+
isReady: true,
|
|
252
|
+
backgroundColor: undefined,
|
|
253
|
+
terminalName: undefined,
|
|
254
|
+
kittyProtocol: false,
|
|
255
|
+
modifyOtherKeys: false,
|
|
256
|
+
};
|
|
257
|
+
// Skip detection if not TTY
|
|
258
|
+
if ((stdin === null || stdin === void 0 ? void 0 : stdin.isTTY) === false) {
|
|
259
|
+
return [2 /*return*/, defaultResult];
|
|
260
|
+
}
|
|
261
|
+
return [2 /*return*/, new Promise(function (resolve) {
|
|
262
|
+
// Assumption: Caller handles raw mode (e.g. via Tinky useStdin)
|
|
263
|
+
var _a;
|
|
264
|
+
var buffer = "";
|
|
265
|
+
var backgroundColor;
|
|
266
|
+
var terminalName;
|
|
267
|
+
var kittyProtocol = false;
|
|
268
|
+
var modifyOtherKeys = false;
|
|
269
|
+
var bgReceived = false;
|
|
270
|
+
var kittyReceived = false;
|
|
271
|
+
var terminalNameReceived = false;
|
|
272
|
+
var modifyOtherKeysReceived = false;
|
|
273
|
+
var deviceAttributesReceived = false;
|
|
274
|
+
var cleanup = function () {
|
|
275
|
+
var _a;
|
|
276
|
+
if (timeoutId) {
|
|
277
|
+
clearTimeout(timeoutId);
|
|
278
|
+
}
|
|
279
|
+
(_a = stdin === null || stdin === void 0 ? void 0 : stdin.off) === null || _a === void 0 ? void 0 : _a.call(stdin, "data", onData);
|
|
280
|
+
resolve({
|
|
281
|
+
isReady: true,
|
|
282
|
+
backgroundColor: backgroundColor,
|
|
283
|
+
terminalName: terminalName,
|
|
284
|
+
kittyProtocol: kittyProtocol,
|
|
285
|
+
modifyOtherKeys: modifyOtherKeys,
|
|
286
|
+
});
|
|
287
|
+
};
|
|
288
|
+
var timeoutId = setTimeout(cleanup, timeout);
|
|
289
|
+
var onData = function (data) {
|
|
290
|
+
buffer += data.toString();
|
|
291
|
+
// Check for background color (OSC 11 response)
|
|
292
|
+
if (!bgReceived) {
|
|
293
|
+
var match = buffer.match(term_features_js_1.Osc11Feature.responseRegex);
|
|
294
|
+
if (match) {
|
|
295
|
+
bgReceived = true;
|
|
296
|
+
backgroundColor = parseColor(match[1], match[2], match[3]);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
// Check for Kitty keyboard protocol support
|
|
300
|
+
if (!kittyReceived && term_features_js_1.KittyFeature.responseRegex.test(buffer)) {
|
|
301
|
+
kittyReceived = true;
|
|
302
|
+
kittyProtocol = true;
|
|
303
|
+
}
|
|
304
|
+
// Check for modifyOtherKeys support
|
|
305
|
+
if (!modifyOtherKeysReceived) {
|
|
306
|
+
var match = buffer.match(term_features_js_1.ModifyOtherKeysFeature.responseRegex);
|
|
307
|
+
if (match) {
|
|
308
|
+
modifyOtherKeysReceived = true;
|
|
309
|
+
var level = parseInt(match[1], 10);
|
|
310
|
+
modifyOtherKeys = level >= 2;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
// Check for terminal name/version
|
|
314
|
+
if (!terminalNameReceived) {
|
|
315
|
+
var match = buffer.match(term_features_js_1.TerminalNameFeature.responseRegex);
|
|
316
|
+
if (match) {
|
|
317
|
+
terminalNameReceived = true;
|
|
318
|
+
terminalName = match[1];
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
// Device Attributes response acts as sentinel - finish when received
|
|
322
|
+
if (!deviceAttributesReceived) {
|
|
323
|
+
var match = buffer.match(term_features_js_1.DeviceAttributesFeature.responseRegex);
|
|
324
|
+
if (match) {
|
|
325
|
+
deviceAttributesReceived = true;
|
|
326
|
+
cleanup();
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
(_a = stdin === null || stdin === void 0 ? void 0 : stdin.on) === null || _a === void 0 ? void 0 : _a.call(stdin, "data", onData);
|
|
331
|
+
try {
|
|
332
|
+
stdout === null || stdout === void 0 ? void 0 : stdout.write(term_features_js_1.KittyFeature.query +
|
|
333
|
+
term_features_js_1.Osc11Feature.query +
|
|
334
|
+
term_features_js_1.TerminalNameFeature.query +
|
|
335
|
+
term_features_js_1.ModifyOtherKeysFeature.query +
|
|
336
|
+
term_features_js_1.DeviceAttributesFeature.query);
|
|
337
|
+
}
|
|
338
|
+
catch (_b) {
|
|
339
|
+
cleanup();
|
|
340
|
+
}
|
|
341
|
+
})];
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Reset module state for testing purposes.
|
|
347
|
+
*
|
|
348
|
+
* This function is provided for test isolation. In the current implementation,
|
|
349
|
+
* there is no module-level state to reset, but this function is maintained
|
|
350
|
+
* for API compatibility.
|
|
351
|
+
*
|
|
352
|
+
* @example
|
|
353
|
+
* ```typescript
|
|
354
|
+
* import { resetForTesting } from "tinky-termcap";
|
|
355
|
+
*
|
|
356
|
+
* beforeEach(() => {
|
|
357
|
+
* resetForTesting();
|
|
358
|
+
* });
|
|
359
|
+
* ```
|
|
360
|
+
*
|
|
361
|
+
* @internal
|
|
362
|
+
*/
|
|
363
|
+
function resetForTesting() {
|
|
364
|
+
// No module-level state to reset in the new design
|
|
365
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detected terminal capabilities.
|
|
3
|
+
*/
|
|
4
|
+
export interface TermcapInfo {
|
|
5
|
+
/** Whether detection is complete */
|
|
6
|
+
isReady: boolean;
|
|
7
|
+
/** Terminal background color in #rrggbb format, or undefined if not detected */
|
|
8
|
+
backgroundColor: string | undefined;
|
|
9
|
+
/** Terminal name/version string, or undefined if not detected */
|
|
10
|
+
terminalName: string | undefined;
|
|
11
|
+
/** Whether Kitty keyboard protocol is supported */
|
|
12
|
+
kittyProtocol: boolean;
|
|
13
|
+
/** Whether modifyOtherKeys mode is supported (level >= 2) */
|
|
14
|
+
modifyOtherKeys: boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Default timeout for capability detection (in milliseconds).
|
|
18
|
+
*/
|
|
19
|
+
export declare const DEFAULT_DETECTION_TIMEOUT = 1000;
|
|
20
|
+
/**
|
|
21
|
+
* Detect terminal capabilities by querying the terminal.
|
|
22
|
+
*
|
|
23
|
+
* This sends escape sequences to query terminal features and parses responses.
|
|
24
|
+
* Should only be called once at app startup.
|
|
25
|
+
*
|
|
26
|
+
* @param timeout - Maximum time to wait for responses (default: 1000ms)
|
|
27
|
+
* @returns Promise resolving to detected capabilities
|
|
28
|
+
*/
|
|
29
|
+
export declare function detectTerminalCapabilities(timeout?: number): Promise<TermcapInfo>;
|
|
30
|
+
/**
|
|
31
|
+
* For testing purposes only - reset module state.
|
|
32
|
+
*/
|
|
33
|
+
export declare function resetForTesting(): void;
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
36
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
37
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
38
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
39
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
40
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
41
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
45
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
46
|
+
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
47
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
48
|
+
function step(op) {
|
|
49
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
50
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
51
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
52
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
53
|
+
switch (op[0]) {
|
|
54
|
+
case 0: case 1: t = op; break;
|
|
55
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
56
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
57
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
58
|
+
default:
|
|
59
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
60
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
61
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
62
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
63
|
+
if (t[2]) _.ops.pop();
|
|
64
|
+
_.trys.pop(); continue;
|
|
65
|
+
}
|
|
66
|
+
op = body.call(thisArg, _);
|
|
67
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
68
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
72
|
+
exports.DEFAULT_DETECTION_TIMEOUT = void 0;
|
|
73
|
+
exports.detectTerminalCapabilities = detectTerminalCapabilities;
|
|
74
|
+
exports.resetForTesting = resetForTesting;
|
|
75
|
+
/* eslint-disable no-control-regex */
|
|
76
|
+
var fs = __importStar(require("node:fs"));
|
|
77
|
+
// ANSI escape sequence query strings
|
|
78
|
+
var KITTY_QUERY = "\x1b[?u";
|
|
79
|
+
var OSC_11_QUERY = "\x1b]11;?\x1b\\";
|
|
80
|
+
var TERMINAL_NAME_QUERY = "\x1b[>q";
|
|
81
|
+
var DEVICE_ATTRIBUTES_QUERY = "\x1b[c";
|
|
82
|
+
var MODIFY_OTHER_KEYS_QUERY = "\x1b[>4;?m";
|
|
83
|
+
// Response regex patterns
|
|
84
|
+
var KITTY_REGEX = /\x1b\[\?(\d+)u/;
|
|
85
|
+
var TERMINAL_NAME_REGEX = /\x1bP>\|(.+?)(\x1b\\|\x07)/;
|
|
86
|
+
var DEVICE_ATTRIBUTES_REGEX = /\x1b\[\?(\d+)(;\d+)*c/;
|
|
87
|
+
var OSC_11_REGEX = /\x1b\]11;rgb:([0-9a-fA-F]{1,4})\/([0-9a-fA-F]{1,4})\/([0-9a-fA-F]{1,4})(\x1b\\|\x07)?/;
|
|
88
|
+
var MODIFY_OTHER_KEYS_REGEX = /\x1b\[>4;(\d+)m/;
|
|
89
|
+
/**
|
|
90
|
+
* Parse RGB color from hex components to #rrggbb format.
|
|
91
|
+
*/
|
|
92
|
+
function parseColor(rHex, gHex, bHex) {
|
|
93
|
+
var parseComponent = function (hex) {
|
|
94
|
+
var val = parseInt(hex, 16);
|
|
95
|
+
if (hex.length === 1)
|
|
96
|
+
return (val / 15) * 255;
|
|
97
|
+
if (hex.length === 2)
|
|
98
|
+
return val;
|
|
99
|
+
if (hex.length === 3)
|
|
100
|
+
return (val / 4095) * 255;
|
|
101
|
+
if (hex.length === 4)
|
|
102
|
+
return (val / 65535) * 255;
|
|
103
|
+
return val;
|
|
104
|
+
};
|
|
105
|
+
var r = parseComponent(rHex);
|
|
106
|
+
var g = parseComponent(gHex);
|
|
107
|
+
var b = parseComponent(bHex);
|
|
108
|
+
var toHex = function (c) { return Math.round(c).toString(16).padStart(2, "0"); };
|
|
109
|
+
return "#".concat(toHex(r)).concat(toHex(g)).concat(toHex(b));
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Default timeout for capability detection (in milliseconds).
|
|
113
|
+
*/
|
|
114
|
+
exports.DEFAULT_DETECTION_TIMEOUT = 1000;
|
|
115
|
+
/**
|
|
116
|
+
* Detect terminal capabilities by querying the terminal.
|
|
117
|
+
*
|
|
118
|
+
* This sends escape sequences to query terminal features and parses responses.
|
|
119
|
+
* Should only be called once at app startup.
|
|
120
|
+
*
|
|
121
|
+
* @param timeout - Maximum time to wait for responses (default: 1000ms)
|
|
122
|
+
* @returns Promise resolving to detected capabilities
|
|
123
|
+
*/
|
|
124
|
+
function detectTerminalCapabilities() {
|
|
125
|
+
return __awaiter(this, arguments, void 0, function (timeout) {
|
|
126
|
+
var defaultResult;
|
|
127
|
+
if (timeout === void 0) { timeout = exports.DEFAULT_DETECTION_TIMEOUT; }
|
|
128
|
+
return __generator(this, function (_a) {
|
|
129
|
+
defaultResult = {
|
|
130
|
+
isReady: true,
|
|
131
|
+
backgroundColor: undefined,
|
|
132
|
+
terminalName: undefined,
|
|
133
|
+
kittyProtocol: false,
|
|
134
|
+
modifyOtherKeys: false,
|
|
135
|
+
};
|
|
136
|
+
// Skip detection if not TTY
|
|
137
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
138
|
+
return [2 /*return*/, defaultResult];
|
|
139
|
+
}
|
|
140
|
+
return [2 /*return*/, new Promise(function (resolve) {
|
|
141
|
+
var originalRawMode = process.stdin.isRaw;
|
|
142
|
+
if (!originalRawMode) {
|
|
143
|
+
process.stdin.setRawMode(true);
|
|
144
|
+
}
|
|
145
|
+
var buffer = "";
|
|
146
|
+
var backgroundColor;
|
|
147
|
+
var terminalName;
|
|
148
|
+
var kittyProtocol = false;
|
|
149
|
+
var modifyOtherKeys = false;
|
|
150
|
+
var bgReceived = false;
|
|
151
|
+
var kittyReceived = false;
|
|
152
|
+
var terminalNameReceived = false;
|
|
153
|
+
var modifyOtherKeysReceived = false;
|
|
154
|
+
var deviceAttributesReceived = false;
|
|
155
|
+
var cleanup = function () {
|
|
156
|
+
if (timeoutId) {
|
|
157
|
+
clearTimeout(timeoutId);
|
|
158
|
+
}
|
|
159
|
+
process.stdin.removeListener("data", onData);
|
|
160
|
+
if (!originalRawMode) {
|
|
161
|
+
process.stdin.setRawMode(false);
|
|
162
|
+
}
|
|
163
|
+
resolve({
|
|
164
|
+
isReady: true,
|
|
165
|
+
backgroundColor: backgroundColor,
|
|
166
|
+
terminalName: terminalName,
|
|
167
|
+
kittyProtocol: kittyProtocol,
|
|
168
|
+
modifyOtherKeys: modifyOtherKeys,
|
|
169
|
+
});
|
|
170
|
+
};
|
|
171
|
+
var timeoutId = setTimeout(cleanup, timeout);
|
|
172
|
+
var onData = function (data) {
|
|
173
|
+
buffer += data.toString();
|
|
174
|
+
// Check for background color (OSC 11 response)
|
|
175
|
+
if (!bgReceived) {
|
|
176
|
+
var match = buffer.match(OSC_11_REGEX);
|
|
177
|
+
if (match) {
|
|
178
|
+
bgReceived = true;
|
|
179
|
+
backgroundColor = parseColor(match[1], match[2], match[3]);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
// Check for Kitty keyboard protocol support
|
|
183
|
+
if (!kittyReceived && KITTY_REGEX.test(buffer)) {
|
|
184
|
+
kittyReceived = true;
|
|
185
|
+
kittyProtocol = true;
|
|
186
|
+
}
|
|
187
|
+
// Check for modifyOtherKeys support
|
|
188
|
+
if (!modifyOtherKeysReceived) {
|
|
189
|
+
var match = buffer.match(MODIFY_OTHER_KEYS_REGEX);
|
|
190
|
+
if (match) {
|
|
191
|
+
modifyOtherKeysReceived = true;
|
|
192
|
+
var level = parseInt(match[1], 10);
|
|
193
|
+
modifyOtherKeys = level >= 2;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// Check for terminal name/version
|
|
197
|
+
if (!terminalNameReceived) {
|
|
198
|
+
var match = buffer.match(TERMINAL_NAME_REGEX);
|
|
199
|
+
if (match) {
|
|
200
|
+
terminalNameReceived = true;
|
|
201
|
+
terminalName = match[1];
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// Device Attributes response acts as sentinel - finish when received
|
|
205
|
+
if (!deviceAttributesReceived) {
|
|
206
|
+
var match = buffer.match(DEVICE_ATTRIBUTES_REGEX);
|
|
207
|
+
if (match) {
|
|
208
|
+
deviceAttributesReceived = true;
|
|
209
|
+
cleanup();
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
process.stdin.on("data", onData);
|
|
214
|
+
try {
|
|
215
|
+
fs.writeSync(process.stdout.fd, KITTY_QUERY +
|
|
216
|
+
OSC_11_QUERY +
|
|
217
|
+
TERMINAL_NAME_QUERY +
|
|
218
|
+
MODIFY_OTHER_KEYS_QUERY +
|
|
219
|
+
DEVICE_ATTRIBUTES_QUERY);
|
|
220
|
+
}
|
|
221
|
+
catch (_a) {
|
|
222
|
+
cleanup();
|
|
223
|
+
}
|
|
224
|
+
})];
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* For testing purposes only - reset module state.
|
|
230
|
+
*/
|
|
231
|
+
function resetForTesting() {
|
|
232
|
+
// No module-level state to reset in the new design
|
|
233
|
+
}
|