@opentui/core 0.4.3 → 0.4.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/audio-stream/demuxer.d.ts +19 -0
- package/audio-stream/icy/demuxer.d.ts +21 -0
- package/audio-stream/icy/metadata.d.ts +1 -0
- package/audio.d.ts +201 -1
- package/chunk-bun-t2myhmwd.js +16382 -0
- package/chunk-bun-t2myhmwd.js.map +62 -0
- package/chunk-bun-tkm837n2.js +10047 -0
- package/chunk-bun-tkm837n2.js.map +32 -0
- package/{index-xt9f071j.js → chunk-node-51kpf0mz.js} +4 -3
- package/{index-xt9f071j.js.map → chunk-node-51kpf0mz.js.map} +3 -3
- package/{index-d5xqskty.js → chunk-node-q0cwyvm9.js} +576 -325
- package/chunk-node-q0cwyvm9.js.map +61 -0
- package/index.bun.js +13086 -0
- package/index.bun.js.map +39 -0
- package/index.d.ts +4 -0
- package/{index.js → index.node.js} +1440 -64
- package/{index.js.map → index.node.js.map} +41 -6
- package/lib/tree-sitter/default-parser-assets.bun.d.ts +1 -0
- package/lib/tree-sitter/default-parsers.d.ts +1 -0
- package/lib/tree-sitter/parsers-config.d.ts +2 -2
- package/lib/tree-sitter/types.d.ts +1 -0
- package/lib/tree-sitter/update-assets.js +124 -58
- package/lib/tree-sitter/update-assets.js.map +3 -3
- package/node-asset-target.d.ts +12 -0
- package/node-assets.d.ts +7 -0
- package/node-assets.js +261 -0
- package/node-assets.js.map +16 -0
- package/package.json +23 -13
- package/parser.worker.js +3350 -188
- package/parser.worker.js.map +18 -12
- package/platform/assets.d.ts +5 -0
- package/platform/runtime-assets.bun.d.ts +4 -0
- package/platform/runtime-assets.node.d.ts +4 -0
- package/platform/runtime.d.ts +5 -1
- package/renderables/text-table-width.d.ts +1 -0
- package/runtime-plugin.js +2 -2
- package/runtime-plugin.js.map +1 -1
- package/testing/bun-test-node.d.ts +1 -0
- package/testing.bun.js +1005 -0
- package/testing.bun.js.map +18 -0
- package/testing.js +2 -2
- package/yoga.bun.js +194 -0
- package/yoga.bun.js.map +9 -0
- package/yoga.js +1 -1
- package/zig-structs.d.ts +47 -0
- package/zig.d.ts +38 -2
- package/index-d5xqskty.js.map +0 -58
package/testing.bun.js
ADDED
|
@@ -0,0 +1,1005 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
ANSI,
|
|
4
|
+
CliRenderer
|
|
5
|
+
} from "./chunk-bun-tkm837n2.js";
|
|
6
|
+
import {
|
|
7
|
+
SystemClock,
|
|
8
|
+
TreeSitterClient
|
|
9
|
+
} from "./chunk-bun-t2myhmwd.js";
|
|
10
|
+
|
|
11
|
+
// src/testing/mock-keys.ts
|
|
12
|
+
import { Buffer as Buffer2 } from "buffer";
|
|
13
|
+
function pasteBytes(text) {
|
|
14
|
+
return Uint8Array.from(Buffer2.from(text));
|
|
15
|
+
}
|
|
16
|
+
var KeyCodes = {
|
|
17
|
+
RETURN: "\r",
|
|
18
|
+
LINEFEED: `
|
|
19
|
+
`,
|
|
20
|
+
TAB: "\t",
|
|
21
|
+
BACKSPACE: "\b",
|
|
22
|
+
DELETE: "\x1B[3~",
|
|
23
|
+
HOME: "\x1B[H",
|
|
24
|
+
END: "\x1B[F",
|
|
25
|
+
ESCAPE: "\x1B",
|
|
26
|
+
ARROW_UP: "\x1B[A",
|
|
27
|
+
ARROW_DOWN: "\x1B[B",
|
|
28
|
+
ARROW_RIGHT: "\x1B[C",
|
|
29
|
+
ARROW_LEFT: "\x1B[D",
|
|
30
|
+
F1: "\x1BOP",
|
|
31
|
+
F2: "\x1BOQ",
|
|
32
|
+
F3: "\x1BOR",
|
|
33
|
+
F4: "\x1BOS",
|
|
34
|
+
F5: "\x1B[15~",
|
|
35
|
+
F6: "\x1B[17~",
|
|
36
|
+
F7: "\x1B[18~",
|
|
37
|
+
F8: "\x1B[19~",
|
|
38
|
+
F9: "\x1B[20~",
|
|
39
|
+
F10: "\x1B[21~",
|
|
40
|
+
F11: "\x1B[23~",
|
|
41
|
+
F12: "\x1B[24~"
|
|
42
|
+
};
|
|
43
|
+
var kittyKeyCodeMap = {
|
|
44
|
+
escape: 27,
|
|
45
|
+
tab: 9,
|
|
46
|
+
return: 13,
|
|
47
|
+
backspace: 127,
|
|
48
|
+
insert: 57348,
|
|
49
|
+
delete: 57349,
|
|
50
|
+
left: 57350,
|
|
51
|
+
right: 57351,
|
|
52
|
+
up: 57352,
|
|
53
|
+
down: 57353,
|
|
54
|
+
pageup: 57354,
|
|
55
|
+
pagedown: 57355,
|
|
56
|
+
home: 57356,
|
|
57
|
+
end: 57357,
|
|
58
|
+
f1: 57364,
|
|
59
|
+
f2: 57365,
|
|
60
|
+
f3: 57366,
|
|
61
|
+
f4: 57367,
|
|
62
|
+
f5: 57368,
|
|
63
|
+
f6: 57369,
|
|
64
|
+
f7: 57370,
|
|
65
|
+
f8: 57371,
|
|
66
|
+
f9: 57372,
|
|
67
|
+
f10: 57373,
|
|
68
|
+
f11: 57374,
|
|
69
|
+
f12: 57375
|
|
70
|
+
};
|
|
71
|
+
function encodeKittySequence(codepoint, modifiers) {
|
|
72
|
+
let modMask = 0;
|
|
73
|
+
if (modifiers?.shift)
|
|
74
|
+
modMask |= 1;
|
|
75
|
+
if (modifiers?.meta)
|
|
76
|
+
modMask |= 2;
|
|
77
|
+
if (modifiers?.ctrl)
|
|
78
|
+
modMask |= 4;
|
|
79
|
+
if (modifiers?.super)
|
|
80
|
+
modMask |= 8;
|
|
81
|
+
if (modifiers?.hyper)
|
|
82
|
+
modMask |= 16;
|
|
83
|
+
if (modMask === 0) {
|
|
84
|
+
return `\x1B[${codepoint}u`;
|
|
85
|
+
} else {
|
|
86
|
+
return `\x1B[${codepoint};${modMask + 1}u`;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function encodeModifyOtherKeysSequence(charCode, modifiers) {
|
|
90
|
+
let modMask = 0;
|
|
91
|
+
if (modifiers?.shift)
|
|
92
|
+
modMask |= 1;
|
|
93
|
+
if (modifiers?.meta)
|
|
94
|
+
modMask |= 2;
|
|
95
|
+
if (modifiers?.ctrl)
|
|
96
|
+
modMask |= 4;
|
|
97
|
+
if (modifiers?.super)
|
|
98
|
+
modMask |= 8;
|
|
99
|
+
if (modifiers?.hyper)
|
|
100
|
+
modMask |= 16;
|
|
101
|
+
if (modMask === 0) {
|
|
102
|
+
return String.fromCharCode(charCode);
|
|
103
|
+
}
|
|
104
|
+
return `\x1B[27;${modMask + 1};${charCode}~`;
|
|
105
|
+
}
|
|
106
|
+
function resolveKeyInput(key) {
|
|
107
|
+
let keyValue;
|
|
108
|
+
let keyName;
|
|
109
|
+
if (typeof key === "string") {
|
|
110
|
+
if (key in KeyCodes) {
|
|
111
|
+
keyValue = KeyCodes[key];
|
|
112
|
+
keyName = key.toLowerCase();
|
|
113
|
+
} else {
|
|
114
|
+
keyValue = key;
|
|
115
|
+
keyName = undefined;
|
|
116
|
+
}
|
|
117
|
+
} else {
|
|
118
|
+
keyValue = KeyCodes[key];
|
|
119
|
+
if (!keyValue) {
|
|
120
|
+
throw new Error(`Unknown key: ${key}`);
|
|
121
|
+
}
|
|
122
|
+
keyName = String(key).toLowerCase();
|
|
123
|
+
}
|
|
124
|
+
return { keyValue, keyName };
|
|
125
|
+
}
|
|
126
|
+
function createMockKeys(renderer, options) {
|
|
127
|
+
const useKittyKeyboard = options?.kittyKeyboard ?? false;
|
|
128
|
+
const useOtherModifiersMode = options?.otherModifiersMode ?? false;
|
|
129
|
+
const effectiveOtherModifiersMode = useOtherModifiersMode && !useKittyKeyboard;
|
|
130
|
+
const pressKeys = async (keys, delayMs = 0) => {
|
|
131
|
+
for (const key of keys) {
|
|
132
|
+
const { keyValue: keyCode } = resolveKeyInput(key);
|
|
133
|
+
renderer.stdin.emit("data", Buffer2.from(keyCode));
|
|
134
|
+
if (delayMs > 0) {
|
|
135
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
const pressKey = (key, modifiers) => {
|
|
140
|
+
if (useKittyKeyboard) {
|
|
141
|
+
let { keyValue, keyName } = resolveKeyInput(key);
|
|
142
|
+
const valueToKeyNameMap = {
|
|
143
|
+
"\b": "backspace",
|
|
144
|
+
"\r": "return",
|
|
145
|
+
"\n": "return",
|
|
146
|
+
"\t": "tab",
|
|
147
|
+
"\x1B": "escape",
|
|
148
|
+
"\x1B[A": "up",
|
|
149
|
+
"\x1B[B": "down",
|
|
150
|
+
"\x1B[C": "right",
|
|
151
|
+
"\x1B[D": "left",
|
|
152
|
+
"\x1B[H": "home",
|
|
153
|
+
"\x1B[F": "end",
|
|
154
|
+
"\x1B[3~": "delete"
|
|
155
|
+
};
|
|
156
|
+
if (keyValue && valueToKeyNameMap[keyValue]) {
|
|
157
|
+
keyName = valueToKeyNameMap[keyValue];
|
|
158
|
+
}
|
|
159
|
+
if (keyName && keyName.startsWith("arrow_")) {
|
|
160
|
+
keyName = keyName.substring(6);
|
|
161
|
+
}
|
|
162
|
+
if (keyName && kittyKeyCodeMap[keyName]) {
|
|
163
|
+
const kittyCode = kittyKeyCodeMap[keyName];
|
|
164
|
+
const sequence = encodeKittySequence(kittyCode, modifiers);
|
|
165
|
+
renderer.stdin.emit("data", Buffer2.from(sequence));
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (keyValue && keyValue.length === 1 && !keyValue.startsWith("\x1B")) {
|
|
169
|
+
const codepoint = keyValue.codePointAt(0);
|
|
170
|
+
if (codepoint) {
|
|
171
|
+
const sequence = encodeKittySequence(codepoint, modifiers);
|
|
172
|
+
renderer.stdin.emit("data", Buffer2.from(sequence));
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (effectiveOtherModifiersMode && modifiers) {
|
|
178
|
+
let { keyValue, keyName } = resolveKeyInput(key);
|
|
179
|
+
const valueToCharCodeMap = {
|
|
180
|
+
"\b": 127,
|
|
181
|
+
"\r": 13,
|
|
182
|
+
"\n": 13,
|
|
183
|
+
"\t": 9,
|
|
184
|
+
"\x1B": 27,
|
|
185
|
+
" ": 32
|
|
186
|
+
};
|
|
187
|
+
let charCode;
|
|
188
|
+
if (keyValue && valueToCharCodeMap[keyValue] !== undefined) {
|
|
189
|
+
charCode = valueToCharCodeMap[keyValue];
|
|
190
|
+
} else if (keyValue && keyValue.length === 1 && !keyValue.startsWith("\x1B")) {
|
|
191
|
+
charCode = keyValue.charCodeAt(0);
|
|
192
|
+
}
|
|
193
|
+
if (charCode !== undefined) {
|
|
194
|
+
const sequence = encodeModifyOtherKeysSequence(charCode, modifiers);
|
|
195
|
+
renderer.stdin.emit("data", Buffer2.from(sequence));
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
let keyCode = resolveKeyInput(key).keyValue;
|
|
200
|
+
if (modifiers) {
|
|
201
|
+
if (keyCode.startsWith("\x1B[") && keyCode.length > 2) {
|
|
202
|
+
const modifier = 1 + (modifiers.shift ? 1 : 0) + (modifiers.meta ? 2 : 0) + (modifiers.ctrl ? 4 : 0) + (modifiers.super ? 8 : 0) + (modifiers.hyper ? 16 : 0);
|
|
203
|
+
if (modifier > 1) {
|
|
204
|
+
const tildeMatch = keyCode.match(/^\x1b\[(\d+)~$/);
|
|
205
|
+
if (tildeMatch) {
|
|
206
|
+
keyCode = `\x1B[${tildeMatch[1]};${modifier}~`;
|
|
207
|
+
} else {
|
|
208
|
+
const ending = keyCode.slice(-1);
|
|
209
|
+
keyCode = `\x1B[1;${modifier}${ending}`;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
} else if (keyCode.length === 1) {
|
|
213
|
+
let char = keyCode;
|
|
214
|
+
if (char === "\t" && modifiers.shift) {
|
|
215
|
+
keyCode = modifiers.meta ? "\x1B\x1B[Z" : "\x1B[Z";
|
|
216
|
+
renderer.stdin.emit("data", Buffer2.from(keyCode));
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
if (char === "\b" && (modifiers.ctrl || modifiers.super || modifiers.hyper)) {
|
|
220
|
+
const modifier = 1 + (modifiers.shift ? 1 : 0) + (modifiers.meta ? 2 : 0) + (modifiers.ctrl ? 4 : 0) + (modifiers.super ? 8 : 0) + (modifiers.hyper ? 16 : 0);
|
|
221
|
+
keyCode = `\x1B[27;${modifier};127~`;
|
|
222
|
+
} else if (modifiers.ctrl) {
|
|
223
|
+
if (char >= "a" && char <= "z") {
|
|
224
|
+
keyCode = String.fromCharCode(char.charCodeAt(0) - 96);
|
|
225
|
+
} else if (char >= "A" && char <= "Z") {
|
|
226
|
+
keyCode = String.fromCharCode(char.charCodeAt(0) - 64);
|
|
227
|
+
} else {
|
|
228
|
+
const specialCtrlMap = {
|
|
229
|
+
"[": "\x1B",
|
|
230
|
+
"\\": "\x1C",
|
|
231
|
+
"]": "\x1D",
|
|
232
|
+
"^": "\x1E",
|
|
233
|
+
_: "\x1F",
|
|
234
|
+
"?": "\x7F",
|
|
235
|
+
"/": "\x1F",
|
|
236
|
+
"-": "\x1F",
|
|
237
|
+
".": "\x1E",
|
|
238
|
+
",": "\x1C",
|
|
239
|
+
"@": "\x00",
|
|
240
|
+
" ": "\x00"
|
|
241
|
+
};
|
|
242
|
+
if (char in specialCtrlMap) {
|
|
243
|
+
keyCode = specialCtrlMap[char];
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
if (modifiers.meta) {
|
|
247
|
+
keyCode = `\x1B${keyCode}`;
|
|
248
|
+
}
|
|
249
|
+
} else {
|
|
250
|
+
if (modifiers.shift && char >= "a" && char <= "z") {
|
|
251
|
+
char = char.toUpperCase();
|
|
252
|
+
}
|
|
253
|
+
if (modifiers.meta) {
|
|
254
|
+
keyCode = `\x1B${char}`;
|
|
255
|
+
} else {
|
|
256
|
+
keyCode = char;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
} else if (modifiers.meta && !keyCode.startsWith("\x1B")) {
|
|
260
|
+
keyCode = `\x1B${keyCode}`;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
renderer.stdin.emit("data", Buffer2.from(keyCode));
|
|
264
|
+
};
|
|
265
|
+
const typeText = async (text, delayMs = 0) => {
|
|
266
|
+
const keys = text.split("");
|
|
267
|
+
await pressKeys(keys, delayMs);
|
|
268
|
+
};
|
|
269
|
+
const pressReturn = (modifiers) => {
|
|
270
|
+
pressKey(KeyCodes.RETURN, modifiers);
|
|
271
|
+
};
|
|
272
|
+
const pressEscape = (modifiers) => {
|
|
273
|
+
pressKey(KeyCodes.ESCAPE, modifiers);
|
|
274
|
+
};
|
|
275
|
+
const pressTab = (modifiers) => {
|
|
276
|
+
pressKey(KeyCodes.TAB, modifiers);
|
|
277
|
+
};
|
|
278
|
+
const pressBackspace = (modifiers) => {
|
|
279
|
+
pressKey(KeyCodes.BACKSPACE, modifiers);
|
|
280
|
+
};
|
|
281
|
+
const pressArrow = (direction, modifiers) => {
|
|
282
|
+
const keyMap = {
|
|
283
|
+
up: KeyCodes.ARROW_UP,
|
|
284
|
+
down: KeyCodes.ARROW_DOWN,
|
|
285
|
+
left: KeyCodes.ARROW_LEFT,
|
|
286
|
+
right: KeyCodes.ARROW_RIGHT
|
|
287
|
+
};
|
|
288
|
+
pressKey(keyMap[direction], modifiers);
|
|
289
|
+
};
|
|
290
|
+
const pressCtrlC = () => {
|
|
291
|
+
pressKey("c", { ctrl: true });
|
|
292
|
+
};
|
|
293
|
+
const pasteBracketedText = (text) => {
|
|
294
|
+
return pressKeys([ANSI.bracketedPasteStart, text, ANSI.bracketedPasteEnd]);
|
|
295
|
+
};
|
|
296
|
+
return {
|
|
297
|
+
pressKeys,
|
|
298
|
+
pressKey,
|
|
299
|
+
typeText,
|
|
300
|
+
pressEnter: pressReturn,
|
|
301
|
+
pressEscape,
|
|
302
|
+
pressTab,
|
|
303
|
+
pressBackspace,
|
|
304
|
+
pressArrow,
|
|
305
|
+
pressCtrlC,
|
|
306
|
+
pasteBracketedText
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// src/testing/mock-mouse.ts
|
|
311
|
+
var MouseButtons = {
|
|
312
|
+
LEFT: 0,
|
|
313
|
+
MIDDLE: 1,
|
|
314
|
+
RIGHT: 2,
|
|
315
|
+
WHEEL_UP: 64,
|
|
316
|
+
WHEEL_DOWN: 65,
|
|
317
|
+
WHEEL_LEFT: 66,
|
|
318
|
+
WHEEL_RIGHT: 67
|
|
319
|
+
};
|
|
320
|
+
function createMockMouse(renderer) {
|
|
321
|
+
let currentPosition = { x: 0, y: 0 };
|
|
322
|
+
let buttonsPressed = new Set;
|
|
323
|
+
const generateMouseEvent = (type, x, y, button = MouseButtons.LEFT, modifiers = {}) => {
|
|
324
|
+
let buttonCode = button;
|
|
325
|
+
if (modifiers.shift)
|
|
326
|
+
buttonCode |= 4;
|
|
327
|
+
if (modifiers.alt)
|
|
328
|
+
buttonCode |= 8;
|
|
329
|
+
if (modifiers.ctrl)
|
|
330
|
+
buttonCode |= 16;
|
|
331
|
+
switch (type) {
|
|
332
|
+
case "move":
|
|
333
|
+
buttonCode = 32 | 3;
|
|
334
|
+
if (modifiers.shift)
|
|
335
|
+
buttonCode |= 4;
|
|
336
|
+
if (modifiers.alt)
|
|
337
|
+
buttonCode |= 8;
|
|
338
|
+
if (modifiers.ctrl)
|
|
339
|
+
buttonCode |= 16;
|
|
340
|
+
break;
|
|
341
|
+
case "drag":
|
|
342
|
+
buttonCode = (buttonsPressed.size > 0 ? Array.from(buttonsPressed)[0] : button) | 32;
|
|
343
|
+
if (modifiers.shift)
|
|
344
|
+
buttonCode |= 4;
|
|
345
|
+
if (modifiers.alt)
|
|
346
|
+
buttonCode |= 8;
|
|
347
|
+
if (modifiers.ctrl)
|
|
348
|
+
buttonCode |= 16;
|
|
349
|
+
break;
|
|
350
|
+
case "scroll":
|
|
351
|
+
break;
|
|
352
|
+
}
|
|
353
|
+
const ansiX = x + 1;
|
|
354
|
+
const ansiY = y + 1;
|
|
355
|
+
let pressRelease = "M";
|
|
356
|
+
if (type === "up" || type === "move" || type === "drag") {
|
|
357
|
+
pressRelease = "m";
|
|
358
|
+
}
|
|
359
|
+
return `\x1B[<${buttonCode};${ansiX};${ansiY}${pressRelease}`;
|
|
360
|
+
};
|
|
361
|
+
const emitMouseEvent = async (type, x, y, button = MouseButtons.LEFT, options = {}) => {
|
|
362
|
+
const { modifiers = {}, delayMs = 0 } = options;
|
|
363
|
+
const eventSequence = generateMouseEvent(type, x, y, button, modifiers);
|
|
364
|
+
renderer.stdin.emit("data", Buffer.from(eventSequence));
|
|
365
|
+
currentPosition = { x, y };
|
|
366
|
+
if (type === "down" && button < 64) {
|
|
367
|
+
buttonsPressed.add(button);
|
|
368
|
+
} else if (type === "up") {
|
|
369
|
+
buttonsPressed.delete(button);
|
|
370
|
+
}
|
|
371
|
+
if (delayMs > 0) {
|
|
372
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
const moveTo = async (x, y, options = {}) => {
|
|
376
|
+
const { button = MouseButtons.LEFT, delayMs = 0, modifiers = {} } = options;
|
|
377
|
+
if (buttonsPressed.size > 0) {
|
|
378
|
+
await emitMouseEvent("drag", x, y, Array.from(buttonsPressed)[0], { modifiers, delayMs });
|
|
379
|
+
} else {
|
|
380
|
+
await emitMouseEvent("move", x, y, button, { modifiers, delayMs });
|
|
381
|
+
}
|
|
382
|
+
currentPosition = { x, y };
|
|
383
|
+
};
|
|
384
|
+
const click = async (x, y, button = MouseButtons.LEFT, options = {}) => {
|
|
385
|
+
const { delayMs = 10, modifiers = {} } = options;
|
|
386
|
+
await emitMouseEvent("down", x, y, button, { modifiers, delayMs });
|
|
387
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
388
|
+
await emitMouseEvent("up", x, y, button, { modifiers, delayMs });
|
|
389
|
+
};
|
|
390
|
+
const doubleClick = async (x, y, button = MouseButtons.LEFT, options = {}) => {
|
|
391
|
+
const { delayMs = 10, modifiers = {} } = options;
|
|
392
|
+
await click(x, y, button, { modifiers, delayMs });
|
|
393
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
394
|
+
await click(x, y, button, { modifiers, delayMs });
|
|
395
|
+
};
|
|
396
|
+
const pressDown = async (x, y, button = MouseButtons.LEFT, options = {}) => {
|
|
397
|
+
const { modifiers = {}, delayMs = 0 } = options;
|
|
398
|
+
await emitMouseEvent("down", x, y, button, { modifiers, delayMs });
|
|
399
|
+
};
|
|
400
|
+
const release = async (x, y, button = MouseButtons.LEFT, options = {}) => {
|
|
401
|
+
const { modifiers = {}, delayMs = 0 } = options;
|
|
402
|
+
await emitMouseEvent("up", x, y, button, { modifiers, delayMs });
|
|
403
|
+
};
|
|
404
|
+
const drag = async (startX, startY, endX, endY, button = MouseButtons.LEFT, options = {}) => {
|
|
405
|
+
const { delayMs = 10, modifiers = {} } = options;
|
|
406
|
+
await pressDown(startX, startY, button, { modifiers });
|
|
407
|
+
const steps = 5;
|
|
408
|
+
const dx = (endX - startX) / steps;
|
|
409
|
+
const dy = (endY - startY) / steps;
|
|
410
|
+
for (let i = 1;i <= steps; i++) {
|
|
411
|
+
const currentX = Math.round(startX + dx * i);
|
|
412
|
+
const currentY = Math.round(startY + dy * i);
|
|
413
|
+
await emitMouseEvent("drag", currentX, currentY, button, { modifiers, delayMs });
|
|
414
|
+
}
|
|
415
|
+
await release(endX, endY, button, { modifiers });
|
|
416
|
+
};
|
|
417
|
+
const scroll = async (x, y, direction, options = {}) => {
|
|
418
|
+
const { modifiers = {}, delayMs = 0 } = options;
|
|
419
|
+
let button;
|
|
420
|
+
switch (direction) {
|
|
421
|
+
case "up":
|
|
422
|
+
button = MouseButtons.WHEEL_UP;
|
|
423
|
+
break;
|
|
424
|
+
case "down":
|
|
425
|
+
button = MouseButtons.WHEEL_DOWN;
|
|
426
|
+
break;
|
|
427
|
+
case "left":
|
|
428
|
+
button = MouseButtons.WHEEL_LEFT;
|
|
429
|
+
break;
|
|
430
|
+
case "right":
|
|
431
|
+
button = MouseButtons.WHEEL_RIGHT;
|
|
432
|
+
break;
|
|
433
|
+
}
|
|
434
|
+
await emitMouseEvent("scroll", x, y, button, { modifiers, delayMs });
|
|
435
|
+
};
|
|
436
|
+
const getCurrentPosition = () => {
|
|
437
|
+
return { ...currentPosition };
|
|
438
|
+
};
|
|
439
|
+
const getPressedButtons = () => {
|
|
440
|
+
return Array.from(buttonsPressed);
|
|
441
|
+
};
|
|
442
|
+
return {
|
|
443
|
+
moveTo,
|
|
444
|
+
click,
|
|
445
|
+
doubleClick,
|
|
446
|
+
pressDown,
|
|
447
|
+
release,
|
|
448
|
+
drag,
|
|
449
|
+
scroll,
|
|
450
|
+
getCurrentPosition,
|
|
451
|
+
getPressedButtons,
|
|
452
|
+
emitMouseEvent
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// src/testing/test-streams.ts
|
|
457
|
+
import { Readable, Writable } from "stream";
|
|
458
|
+
|
|
459
|
+
class TestWriteStream extends Writable {
|
|
460
|
+
isTTY = true;
|
|
461
|
+
columns;
|
|
462
|
+
rows;
|
|
463
|
+
constructor(columns = 80, rows = 24) {
|
|
464
|
+
super();
|
|
465
|
+
this.columns = columns;
|
|
466
|
+
this.rows = rows;
|
|
467
|
+
}
|
|
468
|
+
_write(_chunk, _encoding, callback) {
|
|
469
|
+
callback();
|
|
470
|
+
}
|
|
471
|
+
getColorDepth() {
|
|
472
|
+
return 24;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
function createTestStdin() {
|
|
476
|
+
return new Readable({ read() {} });
|
|
477
|
+
}
|
|
478
|
+
function createTestStdout(columns = 80, rows = 24) {
|
|
479
|
+
return new TestWriteStream(columns, rows);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// src/testing/test-renderer.ts
|
|
483
|
+
var decoder = new TextDecoder;
|
|
484
|
+
var DEFAULT_MAX_PASSES = 20;
|
|
485
|
+
var DEFAULT_MAX_VISUAL_IDLE_FRAMES = 20;
|
|
486
|
+
var DEFAULT_QUIET_FRAMES = 1;
|
|
487
|
+
async function drainImmediateWork() {
|
|
488
|
+
await Promise.resolve();
|
|
489
|
+
await new Promise((resolve) => process.nextTick(resolve));
|
|
490
|
+
await Promise.resolve();
|
|
491
|
+
}
|
|
492
|
+
function normalizePositiveInteger(value, fallback) {
|
|
493
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
494
|
+
return fallback;
|
|
495
|
+
}
|
|
496
|
+
return Math.floor(value);
|
|
497
|
+
}
|
|
498
|
+
function createWaitError(renderer, message, frame) {
|
|
499
|
+
const stats = renderer.getStats();
|
|
500
|
+
const scheduler = renderer.getSchedulerState();
|
|
501
|
+
const details = [
|
|
502
|
+
message,
|
|
503
|
+
`frameId: ${renderer.frameId}`,
|
|
504
|
+
`nativeFrameCount: ${stats.nativeFrameCount}`,
|
|
505
|
+
`cellsUpdated: ${stats.cellsUpdated}`,
|
|
506
|
+
`isRunning: ${scheduler.isRunning}`,
|
|
507
|
+
`isRendering: ${scheduler.isRendering}`,
|
|
508
|
+
`hasScheduledRender: ${scheduler.hasScheduledRender}`
|
|
509
|
+
];
|
|
510
|
+
if (frame !== undefined) {
|
|
511
|
+
details.push(`lastFrame:
|
|
512
|
+
${frame}`);
|
|
513
|
+
}
|
|
514
|
+
return new Error(details.join(`
|
|
515
|
+
`));
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
class TestExternalOutputRecorder {
|
|
519
|
+
commits = [];
|
|
520
|
+
constructor(renderer) {
|
|
521
|
+
renderer.on("external_output" /* EXTERNAL_OUTPUT */, this.record);
|
|
522
|
+
renderer.once("destroy" /* DESTROY */, () => {
|
|
523
|
+
renderer.off("external_output" /* EXTERNAL_OUTPUT */, this.record);
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
record = (event) => {
|
|
527
|
+
const raw = decoder.decode(event.snapshot.getRealCharBytes(false));
|
|
528
|
+
const rows = Array.from({ length: event.snapshot.height }, (_, index) => raw.slice(index * event.snapshot.width, (index + 1) * event.snapshot.width).trimEnd());
|
|
529
|
+
this.commits.push({
|
|
530
|
+
text: rows.join(`
|
|
531
|
+
`),
|
|
532
|
+
rows,
|
|
533
|
+
width: event.snapshot.width,
|
|
534
|
+
height: event.snapshot.height,
|
|
535
|
+
rowColumns: event.rowColumns,
|
|
536
|
+
startOnNewLine: event.startOnNewLine,
|
|
537
|
+
trailingNewline: event.trailingNewline
|
|
538
|
+
});
|
|
539
|
+
};
|
|
540
|
+
take() {
|
|
541
|
+
const commits = this.commits;
|
|
542
|
+
this.commits = [];
|
|
543
|
+
return commits;
|
|
544
|
+
}
|
|
545
|
+
takeText() {
|
|
546
|
+
return this.take().flatMap((commit) => commit.rows).join(`
|
|
547
|
+
`);
|
|
548
|
+
}
|
|
549
|
+
clear() {
|
|
550
|
+
this.commits = [];
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
function waitForNextFrameOrIdle(renderer) {
|
|
554
|
+
const scheduler = renderer.getSchedulerState();
|
|
555
|
+
if (!scheduler.isRunning && !scheduler.isRendering && !scheduler.hasScheduledRender) {
|
|
556
|
+
return Promise.resolve(null);
|
|
557
|
+
}
|
|
558
|
+
return new Promise((resolve) => {
|
|
559
|
+
let settled = false;
|
|
560
|
+
const cleanup = () => {
|
|
561
|
+
renderer.off("frame" /* FRAME */, onFrame);
|
|
562
|
+
renderer.off("destroy" /* DESTROY */, onDestroy);
|
|
563
|
+
};
|
|
564
|
+
const finish = (event) => {
|
|
565
|
+
if (settled)
|
|
566
|
+
return;
|
|
567
|
+
settled = true;
|
|
568
|
+
cleanup();
|
|
569
|
+
resolve(event);
|
|
570
|
+
};
|
|
571
|
+
const onFrame = (event) => {
|
|
572
|
+
finish(event);
|
|
573
|
+
};
|
|
574
|
+
const onDestroy = () => {
|
|
575
|
+
finish(null);
|
|
576
|
+
};
|
|
577
|
+
renderer.on("frame" /* FRAME */, onFrame);
|
|
578
|
+
renderer.once("destroy" /* DESTROY */, onDestroy);
|
|
579
|
+
if (!scheduler.isRunning) {
|
|
580
|
+
renderer.idle().then(() => finish(null));
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
async function createTestRenderer(options) {
|
|
585
|
+
const useKittyKeyboard = options.kittyKeyboard ? { events: true } : options.useKittyKeyboard;
|
|
586
|
+
const renderer = await setupTestRenderer({
|
|
587
|
+
...options,
|
|
588
|
+
useKittyKeyboard,
|
|
589
|
+
screenMode: options.screenMode ?? "main-screen",
|
|
590
|
+
footerHeight: options.footerHeight ?? 12,
|
|
591
|
+
consoleMode: options.consoleMode ?? "disabled",
|
|
592
|
+
externalOutputMode: options.externalOutputMode ?? "passthrough"
|
|
593
|
+
});
|
|
594
|
+
const externalOutput = new TestExternalOutputRecorder(renderer);
|
|
595
|
+
const mockInput = createMockKeys(renderer, {
|
|
596
|
+
kittyKeyboard: options.kittyKeyboard,
|
|
597
|
+
otherModifiersMode: options.otherModifiersMode
|
|
598
|
+
});
|
|
599
|
+
const mockMouse = createMockMouse(renderer);
|
|
600
|
+
const renderOnce = async () => {
|
|
601
|
+
const feed = renderer._feed;
|
|
602
|
+
if (feed?.isBackpressured()) {
|
|
603
|
+
await feed.idle();
|
|
604
|
+
}
|
|
605
|
+
await renderer.loop();
|
|
606
|
+
};
|
|
607
|
+
const captureCharFrame = () => {
|
|
608
|
+
const currentBuffer = renderer.currentRenderBuffer;
|
|
609
|
+
const frameBytes = currentBuffer.getRealCharBytes(true);
|
|
610
|
+
return decoder.decode(frameBytes);
|
|
611
|
+
};
|
|
612
|
+
const waitForVisualIdle = async (waitOptions = {}) => {
|
|
613
|
+
const maxFrames = normalizePositiveInteger(waitOptions.maxFrames, DEFAULT_MAX_VISUAL_IDLE_FRAMES);
|
|
614
|
+
const quietFrames = normalizePositiveInteger(waitOptions.quietFrames, DEFAULT_QUIET_FRAMES);
|
|
615
|
+
let consecutiveQuietFrames = 0;
|
|
616
|
+
for (let frame = 0;frame < maxFrames; frame++) {
|
|
617
|
+
await drainImmediateWork();
|
|
618
|
+
const scheduler2 = renderer.getSchedulerState();
|
|
619
|
+
if (!scheduler2.isRunning && !scheduler2.isRendering && !scheduler2.hasScheduledRender) {
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
const event = await waitForNextFrameOrIdle(renderer);
|
|
623
|
+
if (!event) {
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
if (renderer.getNativeStats().cellsUpdated === 0) {
|
|
627
|
+
consecutiveQuietFrames++;
|
|
628
|
+
if (consecutiveQuietFrames >= quietFrames) {
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
} else {
|
|
632
|
+
consecutiveQuietFrames = 0;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
await drainImmediateWork();
|
|
636
|
+
const scheduler = renderer.getSchedulerState();
|
|
637
|
+
if (!scheduler.isRunning && !scheduler.isRendering && !scheduler.hasScheduledRender) {
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
throw createWaitError(renderer, `Timed out waiting for visual idle after ${maxFrames} frames`);
|
|
641
|
+
};
|
|
642
|
+
const flush = async (flushOptions = {}) => {
|
|
643
|
+
await waitForVisualIdle({ maxFrames: normalizePositiveInteger(flushOptions.maxPasses, DEFAULT_MAX_PASSES) });
|
|
644
|
+
};
|
|
645
|
+
const waitFor = async (predicate, waitOptions = {}) => {
|
|
646
|
+
const maxPasses = normalizePositiveInteger(waitOptions.maxPasses, DEFAULT_MAX_PASSES);
|
|
647
|
+
for (let pass = 0;pass <= maxPasses; pass++) {
|
|
648
|
+
await drainImmediateWork();
|
|
649
|
+
if (await predicate()) {
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
if (pass === maxPasses) {
|
|
653
|
+
break;
|
|
654
|
+
}
|
|
655
|
+
const scheduler = renderer.getSchedulerState();
|
|
656
|
+
if (!scheduler.isRunning && !scheduler.isRendering && !scheduler.hasScheduledRender) {
|
|
657
|
+
break;
|
|
658
|
+
}
|
|
659
|
+
await waitForNextFrameOrIdle(renderer);
|
|
660
|
+
}
|
|
661
|
+
throw createWaitError(renderer, `Timed out waiting for predicate after ${maxPasses} passes`);
|
|
662
|
+
};
|
|
663
|
+
const waitForFrame = async (predicate, waitOptions = {}) => {
|
|
664
|
+
const maxPasses = normalizePositiveInteger(waitOptions.maxPasses, DEFAULT_MAX_PASSES);
|
|
665
|
+
let frame = captureCharFrame();
|
|
666
|
+
for (let pass = 0;pass <= maxPasses; pass++) {
|
|
667
|
+
await drainImmediateWork();
|
|
668
|
+
frame = captureCharFrame();
|
|
669
|
+
if (await predicate(frame)) {
|
|
670
|
+
return frame;
|
|
671
|
+
}
|
|
672
|
+
if (pass === maxPasses) {
|
|
673
|
+
break;
|
|
674
|
+
}
|
|
675
|
+
const scheduler = renderer.getSchedulerState();
|
|
676
|
+
if (!scheduler.isRunning && !scheduler.isRendering && !scheduler.hasScheduledRender) {
|
|
677
|
+
break;
|
|
678
|
+
}
|
|
679
|
+
await waitForNextFrameOrIdle(renderer);
|
|
680
|
+
}
|
|
681
|
+
frame = captureCharFrame();
|
|
682
|
+
throw createWaitError(renderer, `Timed out waiting for frame predicate after ${maxPasses} passes`, frame);
|
|
683
|
+
};
|
|
684
|
+
return {
|
|
685
|
+
renderer,
|
|
686
|
+
mockInput,
|
|
687
|
+
mockMouse,
|
|
688
|
+
renderOnce,
|
|
689
|
+
flush,
|
|
690
|
+
waitFor,
|
|
691
|
+
waitForFrame,
|
|
692
|
+
waitForVisualIdle,
|
|
693
|
+
externalOutput,
|
|
694
|
+
getNativeStats: () => renderer.getNativeStats(),
|
|
695
|
+
captureCharFrame,
|
|
696
|
+
captureSpans: () => {
|
|
697
|
+
const currentBuffer = renderer.currentRenderBuffer;
|
|
698
|
+
const lines = currentBuffer.getSpanLines();
|
|
699
|
+
const cursorState = renderer.getCursorState();
|
|
700
|
+
return {
|
|
701
|
+
cols: currentBuffer.width,
|
|
702
|
+
rows: currentBuffer.height,
|
|
703
|
+
cursor: [cursorState.x, cursorState.y],
|
|
704
|
+
lines
|
|
705
|
+
};
|
|
706
|
+
},
|
|
707
|
+
resize: (width, height) => {
|
|
708
|
+
renderer.processResize(width, height);
|
|
709
|
+
}
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
async function setupTestRenderer(config) {
|
|
713
|
+
const stdin = config.stdin || createTestStdin();
|
|
714
|
+
const width = config.width || config.stdout?.columns || process.stdout.columns || 80;
|
|
715
|
+
const height = config.height || config.stdout?.rows || process.stdout.rows || 24;
|
|
716
|
+
const stdout = config.stdout || createTestStdout(width, height);
|
|
717
|
+
return new CliRenderer(stdin, stdout, width, height, {
|
|
718
|
+
...config,
|
|
719
|
+
bufferedOutput: config.bufferedOutput ?? "memory"
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
// src/testing/mock-tree-sitter-client.ts
|
|
723
|
+
class MockTreeSitterClient extends TreeSitterClient {
|
|
724
|
+
_highlightPromises = [];
|
|
725
|
+
_mockResult = { highlights: [] };
|
|
726
|
+
_autoResolveTimeout;
|
|
727
|
+
_clock;
|
|
728
|
+
constructor(options) {
|
|
729
|
+
super({ dataPath: "/tmp/mock" }, { autoStartWorker: false });
|
|
730
|
+
this._autoResolveTimeout = options?.autoResolveTimeout;
|
|
731
|
+
this._clock = options?.clock ?? new SystemClock;
|
|
732
|
+
}
|
|
733
|
+
async destroy() {
|
|
734
|
+
this.resolveAllHighlightOnce();
|
|
735
|
+
await super.destroy();
|
|
736
|
+
}
|
|
737
|
+
async highlightOnce(content, filetype) {
|
|
738
|
+
const { promise, resolve } = Promise.withResolvers();
|
|
739
|
+
let timeout;
|
|
740
|
+
if (this._autoResolveTimeout !== undefined) {
|
|
741
|
+
timeout = this._clock.setTimeout(() => {
|
|
742
|
+
const index = this._highlightPromises.findIndex((p) => p.promise === promise);
|
|
743
|
+
if (index !== -1) {
|
|
744
|
+
resolve(this._mockResult);
|
|
745
|
+
this._highlightPromises.splice(index, 1);
|
|
746
|
+
}
|
|
747
|
+
}, this._autoResolveTimeout);
|
|
748
|
+
}
|
|
749
|
+
this._highlightPromises.push({ promise, resolve, timeout });
|
|
750
|
+
return promise;
|
|
751
|
+
}
|
|
752
|
+
setMockResult(result) {
|
|
753
|
+
this._mockResult = result;
|
|
754
|
+
}
|
|
755
|
+
resolveHighlightOnce(index = 0) {
|
|
756
|
+
if (index >= 0 && index < this._highlightPromises.length) {
|
|
757
|
+
const item = this._highlightPromises[index];
|
|
758
|
+
if (item.timeout) {
|
|
759
|
+
this._clock.clearTimeout(item.timeout);
|
|
760
|
+
}
|
|
761
|
+
item.resolve(this._mockResult);
|
|
762
|
+
this._highlightPromises.splice(index, 1);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
resolveAllHighlightOnce() {
|
|
766
|
+
for (const { resolve, timeout } of this._highlightPromises) {
|
|
767
|
+
if (timeout) {
|
|
768
|
+
this._clock.clearTimeout(timeout);
|
|
769
|
+
}
|
|
770
|
+
resolve(this._mockResult);
|
|
771
|
+
}
|
|
772
|
+
this._highlightPromises = [];
|
|
773
|
+
}
|
|
774
|
+
isHighlighting() {
|
|
775
|
+
return this._highlightPromises.length > 0;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
// src/testing/terminal-capabilities.ts
|
|
779
|
+
function createTerminalCapabilities(overrides = {}) {
|
|
780
|
+
return {
|
|
781
|
+
kitty_keyboard: false,
|
|
782
|
+
kitty_graphics: false,
|
|
783
|
+
rgb: false,
|
|
784
|
+
ansi256: false,
|
|
785
|
+
unicode: "unicode",
|
|
786
|
+
sgr_pixels: false,
|
|
787
|
+
color_scheme_updates: false,
|
|
788
|
+
explicit_width: false,
|
|
789
|
+
scaled_text: false,
|
|
790
|
+
sixel: false,
|
|
791
|
+
focus_tracking: false,
|
|
792
|
+
sync: false,
|
|
793
|
+
bracketed_paste: false,
|
|
794
|
+
hyperlinks: false,
|
|
795
|
+
osc52: false,
|
|
796
|
+
osc52_support: "unknown",
|
|
797
|
+
notifications: false,
|
|
798
|
+
explicit_cursor_positioning: false,
|
|
799
|
+
remote: false,
|
|
800
|
+
multiplexer: "none",
|
|
801
|
+
...overrides,
|
|
802
|
+
terminal: {
|
|
803
|
+
name: "",
|
|
804
|
+
version: "",
|
|
805
|
+
from_xtversion: false,
|
|
806
|
+
...overrides.terminal
|
|
807
|
+
}
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
function setRendererCapabilities(renderer, overrides = {}) {
|
|
811
|
+
const capabilities = createTerminalCapabilities(overrides);
|
|
812
|
+
renderer._capabilities = capabilities;
|
|
813
|
+
return capabilities;
|
|
814
|
+
}
|
|
815
|
+
// src/testing/spy.ts
|
|
816
|
+
function createSpy() {
|
|
817
|
+
const calls = [];
|
|
818
|
+
const spy = (...args) => {
|
|
819
|
+
calls.push(args);
|
|
820
|
+
};
|
|
821
|
+
spy.calls = calls;
|
|
822
|
+
spy.callCount = () => calls.length;
|
|
823
|
+
spy.calledWith = (...expected) => {
|
|
824
|
+
return calls.some((call) => JSON.stringify(call) === JSON.stringify(expected));
|
|
825
|
+
};
|
|
826
|
+
spy.reset = () => calls.length = 0;
|
|
827
|
+
return spy;
|
|
828
|
+
}
|
|
829
|
+
// src/testing/manual-clock.ts
|
|
830
|
+
function compareTimers(left, right) {
|
|
831
|
+
if (left.fireAt !== right.fireAt) {
|
|
832
|
+
return left.fireAt - right.fireAt;
|
|
833
|
+
}
|
|
834
|
+
return left.order - right.order;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
class ManualClock {
|
|
838
|
+
time = 0;
|
|
839
|
+
nextId = 1;
|
|
840
|
+
nextOrder = 0;
|
|
841
|
+
timers = new Map;
|
|
842
|
+
now() {
|
|
843
|
+
return this.time;
|
|
844
|
+
}
|
|
845
|
+
setTime(time) {
|
|
846
|
+
const targetTime = Math.floor(time);
|
|
847
|
+
if (targetTime >= this.time) {
|
|
848
|
+
this.advance(targetTime - this.time);
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
this.time = targetTime;
|
|
852
|
+
}
|
|
853
|
+
setTimeout(fn, delayMs) {
|
|
854
|
+
return this.schedule(fn, delayMs, false);
|
|
855
|
+
}
|
|
856
|
+
clearTimeout(handle) {
|
|
857
|
+
this.timers.delete(Number(handle));
|
|
858
|
+
}
|
|
859
|
+
setInterval(fn, delayMs) {
|
|
860
|
+
return this.schedule(fn, delayMs, true);
|
|
861
|
+
}
|
|
862
|
+
clearInterval(handle) {
|
|
863
|
+
this.clearTimeout(handle);
|
|
864
|
+
}
|
|
865
|
+
advance(delayMs) {
|
|
866
|
+
const targetTime = this.time + Math.max(0, Math.floor(delayMs));
|
|
867
|
+
while (true) {
|
|
868
|
+
const nextTimer = this.peekNextTimer();
|
|
869
|
+
if (!nextTimer || nextTimer.fireAt > targetTime) {
|
|
870
|
+
break;
|
|
871
|
+
}
|
|
872
|
+
this.timers.delete(nextTimer.id);
|
|
873
|
+
this.time = nextTimer.fireAt;
|
|
874
|
+
nextTimer.fn();
|
|
875
|
+
if (nextTimer.repeat && !this.timers.has(nextTimer.id)) {
|
|
876
|
+
this.timers.set(nextTimer.id, {
|
|
877
|
+
...nextTimer,
|
|
878
|
+
fireAt: this.time + nextTimer.delayMs,
|
|
879
|
+
order: this.nextOrder++
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
this.time = targetTime;
|
|
884
|
+
}
|
|
885
|
+
runAll() {
|
|
886
|
+
while (true) {
|
|
887
|
+
const nextTimer = this.peekNextTimer();
|
|
888
|
+
if (!nextTimer) {
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
this.advance(nextTimer.fireAt - this.time);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
schedule(fn, delayMs, repeat) {
|
|
895
|
+
const id = this.nextId++;
|
|
896
|
+
const normalizedDelay = Math.max(0, Math.floor(delayMs));
|
|
897
|
+
this.timers.set(id, {
|
|
898
|
+
id,
|
|
899
|
+
fireAt: this.time + normalizedDelay,
|
|
900
|
+
order: this.nextOrder++,
|
|
901
|
+
delayMs: normalizedDelay,
|
|
902
|
+
repeat,
|
|
903
|
+
fn
|
|
904
|
+
});
|
|
905
|
+
return id;
|
|
906
|
+
}
|
|
907
|
+
peekNextTimer() {
|
|
908
|
+
let nextTimer = null;
|
|
909
|
+
for (const timer of this.timers.values()) {
|
|
910
|
+
if (!nextTimer || compareTimers(timer, nextTimer) < 0) {
|
|
911
|
+
nextTimer = timer;
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
return nextTimer;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
// src/testing/test-recorder.ts
|
|
918
|
+
class TestRecorder {
|
|
919
|
+
renderer;
|
|
920
|
+
frames = [];
|
|
921
|
+
recording = false;
|
|
922
|
+
frameNumber = 0;
|
|
923
|
+
startTime = 0;
|
|
924
|
+
decoder = new TextDecoder;
|
|
925
|
+
recordBuffers;
|
|
926
|
+
now;
|
|
927
|
+
onFrame = () => {
|
|
928
|
+
if (!this.recording)
|
|
929
|
+
return;
|
|
930
|
+
this.captureFrame();
|
|
931
|
+
};
|
|
932
|
+
constructor(renderer, options) {
|
|
933
|
+
this.renderer = renderer;
|
|
934
|
+
this.recordBuffers = options?.recordBuffers || {};
|
|
935
|
+
this.now = options?.now ?? (() => performance.now());
|
|
936
|
+
}
|
|
937
|
+
rec() {
|
|
938
|
+
if (this.recording) {
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
this.recording = true;
|
|
942
|
+
this.frames = [];
|
|
943
|
+
this.frameNumber = 0;
|
|
944
|
+
this.startTime = this.now();
|
|
945
|
+
this.renderer.on("frame" /* FRAME */, this.onFrame);
|
|
946
|
+
}
|
|
947
|
+
stop() {
|
|
948
|
+
if (!this.recording) {
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
this.recording = false;
|
|
952
|
+
this.renderer.off("frame" /* FRAME */, this.onFrame);
|
|
953
|
+
}
|
|
954
|
+
get recordedFrames() {
|
|
955
|
+
return [...this.frames];
|
|
956
|
+
}
|
|
957
|
+
clear() {
|
|
958
|
+
this.frames = [];
|
|
959
|
+
this.frameNumber = 0;
|
|
960
|
+
}
|
|
961
|
+
get isRecording() {
|
|
962
|
+
return this.recording;
|
|
963
|
+
}
|
|
964
|
+
captureFrame() {
|
|
965
|
+
const currentBuffer = this.renderer.currentRenderBuffer;
|
|
966
|
+
const frameBytes = currentBuffer.getRealCharBytes(true);
|
|
967
|
+
const frame = this.decoder.decode(frameBytes);
|
|
968
|
+
const recordedFrame = {
|
|
969
|
+
frame,
|
|
970
|
+
timestamp: this.now() - this.startTime,
|
|
971
|
+
frameNumber: this.frameNumber++
|
|
972
|
+
};
|
|
973
|
+
if (this.recordBuffers.fg || this.recordBuffers.bg || this.recordBuffers.attributes) {
|
|
974
|
+
const buffers = currentBuffer.buffers;
|
|
975
|
+
recordedFrame.buffers = {};
|
|
976
|
+
if (this.recordBuffers.fg) {
|
|
977
|
+
recordedFrame.buffers.fg = new Uint16Array(buffers.fg);
|
|
978
|
+
}
|
|
979
|
+
if (this.recordBuffers.bg) {
|
|
980
|
+
recordedFrame.buffers.bg = new Uint16Array(buffers.bg);
|
|
981
|
+
}
|
|
982
|
+
if (this.recordBuffers.attributes) {
|
|
983
|
+
recordedFrame.buffers.attributes = new Uint8Array(buffers.attributes);
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
this.frames.push(recordedFrame);
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
export {
|
|
990
|
+
setRendererCapabilities,
|
|
991
|
+
pasteBytes,
|
|
992
|
+
createTestRenderer,
|
|
993
|
+
createTerminalCapabilities,
|
|
994
|
+
createSpy,
|
|
995
|
+
createMockMouse,
|
|
996
|
+
createMockKeys,
|
|
997
|
+
TestRecorder,
|
|
998
|
+
MouseButtons,
|
|
999
|
+
MockTreeSitterClient,
|
|
1000
|
+
ManualClock,
|
|
1001
|
+
KeyCodes
|
|
1002
|
+
};
|
|
1003
|
+
|
|
1004
|
+
//# debugId=0357928BEE71C0AA64756E2164756E21
|
|
1005
|
+
//# sourceMappingURL=testing.bun.js.map
|