ptywright 0.4.0 → 0.5.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.
@@ -143,6 +143,43 @@ function functionKeySpec(key) {
143
143
  };
144
144
  }
145
145
  //#endregion
146
+ //#region src/terminal/mouse.ts
147
+ const ESC$2 = "\x1B";
148
+ function encodeSgrMouse(event) {
149
+ const x = clampInt(event.x, 1, 500);
150
+ const y = clampInt(event.y, 1, 300);
151
+ if (event.action === "click") return `${encodeSingleSgrMouse({
152
+ ...event,
153
+ action: "down"
154
+ }, x, y)}${encodeSingleSgrMouse({
155
+ ...event,
156
+ action: "up"
157
+ }, x, y)}`;
158
+ return encodeSingleSgrMouse(event, x, y);
159
+ }
160
+ function encodeSingleSgrMouse(event, x, y) {
161
+ const modifiers = event.modifiers;
162
+ const modifierBits = (modifiers?.shift ? 4 : 0) + (modifiers?.alt ? 8 : 0) + (modifiers?.ctrl ? 16 : 0);
163
+ const buttonCode = buttonToCode(event.button ?? "left");
164
+ if (event.action === "down") return `${ESC$2}[<${buttonCode + modifierBits};${x};${y}M`;
165
+ if (event.action === "up") return `${ESC$2}[<${buttonCode + modifierBits};${x};${y}m`;
166
+ if (event.action === "scroll_up") return `${ESC$2}[<${64 + modifierBits};${x};${y}M`;
167
+ if (event.action === "scroll_down") return `${ESC$2}[<${65 + modifierBits};${x};${y}M`;
168
+ return `${ESC$2}[<${32 + (event.button ? buttonCode : 3) + modifierBits};${x};${y}M`;
169
+ }
170
+ function buttonToCode(button) {
171
+ if (button === "left") return 0;
172
+ if (button === "middle") return 1;
173
+ return 2;
174
+ }
175
+ function clampInt(value, min, max) {
176
+ if (!Number.isFinite(value)) return min;
177
+ const int = Math.trunc(value);
178
+ if (int < min) return min;
179
+ if (int > max) return max;
180
+ return int;
181
+ }
182
+ //#endregion
146
183
  //#region src/terminal/style.ts
147
184
  const DEFAULT_STYLE = {
148
185
  fg: { mode: "default" },
@@ -207,128 +244,64 @@ function extractColor(isDefault, isPalette, isRgb, value) {
207
244
  return { mode: "default" };
208
245
  }
209
246
  //#endregion
210
- //#region src/terminal/ansi.ts
211
- const ESC$2 = "\x1B";
212
- const SGR_RESET = `${ESC$2}[0m`;
213
- function renderAnsiLines(terminal, options) {
214
- const scope = options?.scope ?? "visible";
215
- const trimRight = options?.trimRight ?? false;
216
- const buffer = terminal.buffer.active;
217
- const nullCell = buffer.getNullCell();
218
- let startY = 0;
219
- let count = buffer.length;
220
- if (scope === "visible") {
221
- startY = buffer.viewportY;
222
- count = terminal.rows;
223
- }
224
- const out = [];
225
- for (let i = 0; i < count; i += 1) {
226
- const y = startY + i;
227
- const line = buffer.getLine(y);
228
- out.push(renderLine(line, terminal.cols, nullCell, trimRight));
247
+ //#region src/terminal/snapshot_rows.ts
248
+ function snapshotPlainLine(line, cols, nullCell, trimRight) {
249
+ let endCol = cols;
250
+ if (trimRight) endCol = findMeaningfulEndCol(line, cols, nullCell);
251
+ let out = "";
252
+ for (let x = 0; x < endCol; x += 1) {
253
+ const cell = line?.getCell(x, nullCell);
254
+ if (!cell) {
255
+ out += " ";
256
+ continue;
257
+ }
258
+ if (cell.getWidth() === 0) continue;
259
+ out += cell.getChars() || " ";
229
260
  }
230
261
  return out;
231
262
  }
232
- function renderLine(line, cols, nullCell, trimRight) {
263
+ function snapshotStyleRuns(line, cols, nullCell, trimRight) {
233
264
  let endCol = cols;
234
265
  if (trimRight) endCol = findMeaningfulEndCol(line, cols, nullCell);
235
- let ansi = "";
236
- let plain = "";
237
- let hasStyle = false;
238
- let usedSgr = false;
239
- const defaultKey = styleKey(DEFAULT_STYLE);
240
- let currentKey = defaultKey;
266
+ const out = [];
267
+ let currentKey = null;
268
+ let currentRun = null;
241
269
  for (let x = 0; x < endCol; x += 1) {
242
270
  const cell = line?.getCell(x, nullCell);
243
271
  if (!cell) {
244
- plain += " ";
245
- ansi += " ";
272
+ if (currentRun) {
273
+ out.push(currentRun);
274
+ currentRun = null;
275
+ currentKey = null;
276
+ }
246
277
  continue;
247
278
  }
248
- if (cell.getWidth() === 0) continue;
249
- const chars = cell.getChars() || " ";
279
+ const width = cell.getWidth();
280
+ if (width === 0) continue;
250
281
  const style = extractStyle(cell);
251
- const isDefault = isDefaultStyle(style);
252
- if (!isDefault) hasStyle = true;
282
+ if (isDefaultStyle(style)) {
283
+ if (currentRun) {
284
+ out.push(currentRun);
285
+ currentRun = null;
286
+ currentKey = null;
287
+ }
288
+ continue;
289
+ }
253
290
  const key = styleKey(style);
254
- if (key !== currentKey) {
255
- ansi += isDefault ? SGR_RESET : toSgr(style);
256
- usedSgr = true;
257
- currentKey = key;
291
+ if (currentRun && key === currentKey) {
292
+ currentRun.endCol = x + width;
293
+ continue;
258
294
  }
259
- ansi += chars;
260
- plain += chars;
261
- }
262
- if (usedSgr && currentKey !== defaultKey) ansi += SGR_RESET;
263
- return {
264
- ansi,
265
- plain,
266
- hasStyle
267
- };
268
- }
269
- function toSgr(style) {
270
- const codes = ["0"];
271
- if (style.bold) codes.push("1");
272
- if (style.dim) codes.push("2");
273
- if (style.italic) codes.push("3");
274
- if (style.underline) codes.push("4");
275
- if (style.inverse) codes.push("7");
276
- if (style.strikethrough) codes.push("9");
277
- if (style.fg.mode === "palette") codes.push(`38;5;${style.fg.value}`);
278
- else if (style.fg.mode === "rgb") {
279
- const [r, g, b] = rgb(style.fg.value);
280
- codes.push(`38;2;${r};${g};${b}`);
281
- }
282
- if (style.bg.mode === "palette") codes.push(`48;5;${style.bg.value}`);
283
- else if (style.bg.mode === "rgb") {
284
- const [r, g, b] = rgb(style.bg.value);
285
- codes.push(`48;2;${r};${g};${b}`);
295
+ if (currentRun) out.push(currentRun);
296
+ currentKey = key;
297
+ currentRun = {
298
+ startCol: x,
299
+ endCol: x + width,
300
+ style
301
+ };
286
302
  }
287
- return `${ESC$2}[${codes.join(";")}m`;
288
- }
289
- function rgb(value) {
290
- return [
291
- value >> 16 & 255,
292
- value >> 8 & 255,
293
- value & 255
294
- ];
295
- }
296
- //#endregion
297
- //#region src/terminal/mouse.ts
298
- const ESC$1 = "\x1B";
299
- function encodeSgrMouse(event) {
300
- const x = clampInt(event.x, 1, 500);
301
- const y = clampInt(event.y, 1, 300);
302
- if (event.action === "click") return `${encodeSingleSgrMouse({
303
- ...event,
304
- action: "down"
305
- }, x, y)}${encodeSingleSgrMouse({
306
- ...event,
307
- action: "up"
308
- }, x, y)}`;
309
- return encodeSingleSgrMouse(event, x, y);
310
- }
311
- function encodeSingleSgrMouse(event, x, y) {
312
- const modifiers = event.modifiers;
313
- const modifierBits = (modifiers?.shift ? 4 : 0) + (modifiers?.alt ? 8 : 0) + (modifiers?.ctrl ? 16 : 0);
314
- const buttonCode = buttonToCode(event.button ?? "left");
315
- if (event.action === "down") return `${ESC$1}[<${buttonCode + modifierBits};${x};${y}M`;
316
- if (event.action === "up") return `${ESC$1}[<${buttonCode + modifierBits};${x};${y}m`;
317
- if (event.action === "scroll_up") return `${ESC$1}[<${64 + modifierBits};${x};${y}M`;
318
- if (event.action === "scroll_down") return `${ESC$1}[<${65 + modifierBits};${x};${y}M`;
319
- return `${ESC$1}[<${32 + (event.button ? buttonCode : 3) + modifierBits};${x};${y}M`;
320
- }
321
- function buttonToCode(button) {
322
- if (button === "left") return 0;
323
- if (button === "middle") return 1;
324
- return 2;
325
- }
326
- function clampInt(value, min, max) {
327
- if (!Number.isFinite(value)) return min;
328
- const int = Math.trunc(value);
329
- if (int < min) return min;
330
- if (int > max) return max;
331
- return int;
303
+ if (currentRun) out.push(currentRun);
304
+ return out;
332
305
  }
333
306
  //#endregion
334
307
  //#region src/terminal/snapshot.ts
@@ -383,108 +356,6 @@ function snapshotGrid(terminal, options) {
383
356
  styleRuns: includeStyles ? snapshotVisibleStyleRuns(terminal, trimRight) : void 0
384
357
  };
385
358
  }
386
- function snapshotPlainLine(line, cols, nullCell, trimRight) {
387
- let endCol = cols;
388
- if (trimRight) endCol = findMeaningfulEndCol(line, cols, nullCell);
389
- let out = "";
390
- for (let x = 0; x < endCol; x += 1) {
391
- const cell = line?.getCell(x, nullCell);
392
- if (!cell) {
393
- out += " ";
394
- continue;
395
- }
396
- if (cell.getWidth() === 0) continue;
397
- out += cell.getChars() || " ";
398
- }
399
- return out;
400
- }
401
- function snapshotStyleRuns(line, cols, nullCell, trimRight) {
402
- let endCol = cols;
403
- if (trimRight) endCol = findMeaningfulEndCol(line, cols, nullCell);
404
- const out = [];
405
- let currentKey = null;
406
- let currentRun = null;
407
- for (let x = 0; x < endCol; x += 1) {
408
- const cell = line?.getCell(x, nullCell);
409
- if (!cell) {
410
- if (currentRun) {
411
- out.push(currentRun);
412
- currentRun = null;
413
- currentKey = null;
414
- }
415
- continue;
416
- }
417
- const width = cell.getWidth();
418
- if (width === 0) continue;
419
- const style = extractStyle(cell);
420
- if (isDefaultStyle(style)) {
421
- if (currentRun) {
422
- out.push(currentRun);
423
- currentRun = null;
424
- currentKey = null;
425
- }
426
- continue;
427
- }
428
- const key = styleKey(style);
429
- if (currentRun && key === currentKey) {
430
- currentRun.endCol = x + width;
431
- continue;
432
- }
433
- if (currentRun) out.push(currentRun);
434
- currentKey = key;
435
- currentRun = {
436
- startCol: x,
437
- endCol: x + width,
438
- style
439
- };
440
- }
441
- if (currentRun) out.push(currentRun);
442
- return out;
443
- }
444
- //#endregion
445
- //#region src/terminal/mask.ts
446
- function applyTextMaskRules(lines, rules) {
447
- if (!rules || rules.length === 0) return lines;
448
- const compiled = compileMaskRules(rules);
449
- if (compiled.length === 0) return lines;
450
- return lines.map((line) => applyCompiledMaskRules(line, compiled));
451
- }
452
- function compileMaskRules(rules) {
453
- const compiled = [];
454
- for (const rule of rules) {
455
- if (!rule.regex.trim()) continue;
456
- const preserveLength = rule.preserveLength ?? false;
457
- const replacement = preserveLength ? firstChar(rule.replacement) : rule.replacement ?? "<masked>";
458
- const flags = normalizeFlags(rule.flags);
459
- let regex;
460
- try {
461
- regex = new RegExp(rule.regex, flags);
462
- } catch (error) {
463
- throw new Error(`invalid mask rule regex=${JSON.stringify(rule.regex)} flags=${JSON.stringify(rule.flags ?? "")}: ${error.message}`);
464
- }
465
- compiled.push({
466
- regex,
467
- replacement,
468
- preserveLength
469
- });
470
- }
471
- return compiled;
472
- }
473
- function normalizeFlags(flags) {
474
- const value = flags?.trim() ? flags.trim() : "g";
475
- const set = new Set(value.split(""));
476
- set.add("g");
477
- return [...set].join("");
478
- }
479
- function firstChar(value) {
480
- const trimmed = value?.trim();
481
- return trimmed && trimmed.length > 0 ? trimmed[0] : "█";
482
- }
483
- function applyCompiledMaskRules(line, rules) {
484
- let out = line;
485
- for (const rule of rules) out = out.replace(rule.regex, (match) => rule.preserveLength ? rule.replacement.repeat(match.length) : rule.replacement);
486
- return out;
487
- }
488
359
  //#endregion
489
360
  //#region src/util/hash.ts
490
361
  function fnv1a32(text) {
@@ -496,13 +367,6 @@ function fnv1a32(text) {
496
367
  return (hash >>> 0).toString(16).padStart(8, "0");
497
368
  }
498
369
  //#endregion
499
- //#region src/util/sleep.ts
500
- async function sleep(ms) {
501
- await new Promise((resolve) => {
502
- setTimeout(resolve, ms);
503
- });
504
- }
505
- //#endregion
506
370
  //#region src/trace/asciicast.ts
507
371
  function encodeAsciicast(header, events) {
508
372
  const lines = [JSON.stringify(header)];
@@ -589,6 +453,269 @@ var TraceRecorder = class {
589
453
  }
590
454
  };
591
455
  //#endregion
456
+ //#region src/terminal/ansi.ts
457
+ const ESC$1 = "\x1B";
458
+ const SGR_RESET = `${ESC$1}[0m`;
459
+ function renderAnsiLines(terminal, options) {
460
+ const scope = options?.scope ?? "visible";
461
+ const trimRight = options?.trimRight ?? false;
462
+ const buffer = terminal.buffer.active;
463
+ const nullCell = buffer.getNullCell();
464
+ let startY = 0;
465
+ let count = buffer.length;
466
+ if (scope === "visible") {
467
+ startY = buffer.viewportY;
468
+ count = terminal.rows;
469
+ }
470
+ const out = [];
471
+ for (let i = 0; i < count; i += 1) {
472
+ const y = startY + i;
473
+ const line = buffer.getLine(y);
474
+ out.push(renderLine(line, terminal.cols, nullCell, trimRight));
475
+ }
476
+ return out;
477
+ }
478
+ function renderLine(line, cols, nullCell, trimRight) {
479
+ let endCol = cols;
480
+ if (trimRight) endCol = findMeaningfulEndCol(line, cols, nullCell);
481
+ let ansi = "";
482
+ let plain = "";
483
+ let hasStyle = false;
484
+ let usedSgr = false;
485
+ const defaultKey = styleKey(DEFAULT_STYLE);
486
+ let currentKey = defaultKey;
487
+ for (let x = 0; x < endCol; x += 1) {
488
+ const cell = line?.getCell(x, nullCell);
489
+ if (!cell) {
490
+ plain += " ";
491
+ ansi += " ";
492
+ continue;
493
+ }
494
+ if (cell.getWidth() === 0) continue;
495
+ const chars = cell.getChars() || " ";
496
+ const style = extractStyle(cell);
497
+ const isDefault = isDefaultStyle(style);
498
+ if (!isDefault) hasStyle = true;
499
+ const key = styleKey(style);
500
+ if (key !== currentKey) {
501
+ ansi += isDefault ? SGR_RESET : toSgr(style);
502
+ usedSgr = true;
503
+ currentKey = key;
504
+ }
505
+ ansi += chars;
506
+ plain += chars;
507
+ }
508
+ if (usedSgr && currentKey !== defaultKey) ansi += SGR_RESET;
509
+ return {
510
+ ansi,
511
+ plain,
512
+ hasStyle
513
+ };
514
+ }
515
+ function toSgr(style) {
516
+ const codes = ["0"];
517
+ if (style.bold) codes.push("1");
518
+ if (style.dim) codes.push("2");
519
+ if (style.italic) codes.push("3");
520
+ if (style.underline) codes.push("4");
521
+ if (style.inverse) codes.push("7");
522
+ if (style.strikethrough) codes.push("9");
523
+ if (style.fg.mode === "palette") codes.push(`38;5;${style.fg.value}`);
524
+ else if (style.fg.mode === "rgb") {
525
+ const [r, g, b] = rgb(style.fg.value);
526
+ codes.push(`38;2;${r};${g};${b}`);
527
+ }
528
+ if (style.bg.mode === "palette") codes.push(`48;5;${style.bg.value}`);
529
+ else if (style.bg.mode === "rgb") {
530
+ const [r, g, b] = rgb(style.bg.value);
531
+ codes.push(`48;2;${r};${g};${b}`);
532
+ }
533
+ return `${ESC$1}[${codes.join(";")}m`;
534
+ }
535
+ function rgb(value) {
536
+ return [
537
+ value >> 16 & 255,
538
+ value >> 8 & 255,
539
+ value & 255
540
+ ];
541
+ }
542
+ //#endregion
543
+ //#region src/terminal/mask.ts
544
+ function applyTextMaskRules(lines, rules) {
545
+ if (!rules || rules.length === 0) return lines;
546
+ const compiled = compileMaskRules(rules);
547
+ if (compiled.length === 0) return lines;
548
+ return lines.map((line) => applyCompiledMaskRules(line, compiled));
549
+ }
550
+ function compileMaskRules(rules) {
551
+ const compiled = [];
552
+ for (const rule of rules) {
553
+ if (!rule.regex.trim()) continue;
554
+ const preserveLength = rule.preserveLength ?? false;
555
+ const replacement = preserveLength ? firstChar(rule.replacement) : rule.replacement ?? "<masked>";
556
+ const flags = normalizeFlags(rule.flags);
557
+ let regex;
558
+ try {
559
+ regex = new RegExp(rule.regex, flags);
560
+ } catch (error) {
561
+ throw new Error(`invalid mask rule regex=${JSON.stringify(rule.regex)} flags=${JSON.stringify(rule.flags ?? "")}: ${error.message}`);
562
+ }
563
+ compiled.push({
564
+ regex,
565
+ replacement,
566
+ preserveLength
567
+ });
568
+ }
569
+ return compiled;
570
+ }
571
+ function normalizeFlags(flags) {
572
+ const value = flags?.trim() ? flags.trim() : "g";
573
+ const set = new Set(value.split(""));
574
+ set.add("g");
575
+ return [...set].join("");
576
+ }
577
+ function firstChar(value) {
578
+ const trimmed = value?.trim();
579
+ return trimmed && trimmed.length > 0 ? trimmed[0] : "█";
580
+ }
581
+ function applyCompiledMaskRules(line, rules) {
582
+ let out = line;
583
+ for (const rule of rules) out = out.replace(rule.regex, (match) => rule.preserveLength ? rule.replacement.repeat(match.length) : rule.replacement);
584
+ return out;
585
+ }
586
+ //#endregion
587
+ //#region src/session/terminal_session_snapshot.ts
588
+ function snapshotSessionText(terminal, options) {
589
+ if (options?.maxLines !== void 0 && options.tailLines !== void 0) throw new Error("snapshotText: maxLines and tailLines are mutually exclusive");
590
+ let lines = snapshotLines(terminal, {
591
+ scope: options?.scope,
592
+ trimRight: options?.trimRight
593
+ });
594
+ if (options?.trimBottom ?? true) lines = trimBottomEmptyLines(lines);
595
+ if (options?.maxLines !== void 0) {
596
+ const max = Math.max(0, Math.trunc(options.maxLines));
597
+ lines = lines.slice(0, max);
598
+ }
599
+ if (options?.tailLines !== void 0) {
600
+ const tail = Math.max(0, Math.trunc(options.tailLines));
601
+ lines = lines.slice(Math.max(0, lines.length - tail));
602
+ }
603
+ lines = applyTextMaskRules(lines, options?.mask);
604
+ const text = lines.join("\n");
605
+ return {
606
+ text,
607
+ hash: fnv1a32(text)
608
+ };
609
+ }
610
+ function snapshotSessionAnsi(terminal, options) {
611
+ if (options?.maxLines !== void 0 && options.tailLines !== void 0) throw new Error("snapshotAnsi: maxLines and tailLines are mutually exclusive");
612
+ let lines = renderAnsiLines(terminal, {
613
+ scope: options?.scope,
614
+ trimRight: options?.trimRight
615
+ });
616
+ if (options?.trimBottom ?? true) lines = trimBottomEmptyAnsiLines(lines);
617
+ if (options?.maxLines !== void 0) {
618
+ const max = Math.max(0, Math.trunc(options.maxLines));
619
+ lines = lines.slice(0, max);
620
+ }
621
+ if (options?.tailLines !== void 0) {
622
+ const tail = Math.max(0, Math.trunc(options.tailLines));
623
+ lines = lines.slice(Math.max(0, lines.length - tail));
624
+ }
625
+ if (options?.mask && options.mask.length > 0) {
626
+ const maskedPlain = applyTextMaskRules(lines.map((line) => line.plain), options.mask);
627
+ const maskedAnsi = applyTextMaskRules(lines.map((line) => line.ansi), options.mask);
628
+ lines = lines.map((line, idx) => ({
629
+ ...line,
630
+ plain: maskedPlain[idx] ?? "",
631
+ ansi: maskedAnsi[idx] ?? ""
632
+ }));
633
+ }
634
+ const ansi = lines.map((l) => l.ansi).join("\n");
635
+ return {
636
+ ansi,
637
+ plain: lines.map((l) => l.plain).join("\n"),
638
+ hash: fnv1a32(ansi),
639
+ lines
640
+ };
641
+ }
642
+ function trimBottomEmptyLines(lines) {
643
+ let end = lines.length;
644
+ while (end > 0 && lines[end - 1] === "") end -= 1;
645
+ return end === lines.length ? lines : lines.slice(0, end);
646
+ }
647
+ function trimBottomEmptyAnsiLines(lines) {
648
+ let end = lines.length;
649
+ while (end > 0) {
650
+ const line = lines[end - 1];
651
+ if (!(!line?.hasStyle && (line?.plain ?? "").trim() === "")) break;
652
+ end -= 1;
653
+ }
654
+ return end === lines.length ? lines : lines.slice(0, end);
655
+ }
656
+ //#endregion
657
+ //#region src/util/sleep.ts
658
+ async function sleep(ms) {
659
+ await new Promise((resolve) => {
660
+ setTimeout(resolve, ms);
661
+ });
662
+ }
663
+ //#endregion
664
+ //#region src/session/terminal_session_wait.ts
665
+ async function waitForSessionText(host, args) {
666
+ const startedAt = Date.now();
667
+ let closedSince = null;
668
+ while (Date.now() - startedAt <= args.timeoutMs) {
669
+ const snapshot = await host.snapshotText({
670
+ captureFrame: true,
671
+ scope: args.scope
672
+ });
673
+ if (args.text && snapshot.text.includes(args.text)) return {
674
+ found: true,
675
+ ...snapshot
676
+ };
677
+ if (args.regex && args.regex.test(snapshot.text)) return {
678
+ found: true,
679
+ ...snapshot
680
+ };
681
+ if (host.isClosed()) {
682
+ closedSince ??= Date.now();
683
+ const drainMs = Math.max(500, args.intervalMs * 4);
684
+ if (Date.now() - closedSince >= drainMs) break;
685
+ }
686
+ await sleep(args.intervalMs);
687
+ }
688
+ return {
689
+ found: false,
690
+ ...await host.snapshotText({
691
+ captureFrame: true,
692
+ scope: args.scope
693
+ })
694
+ };
695
+ }
696
+ async function waitForStableSessionScreen(host, args) {
697
+ const startedAt = Date.now();
698
+ let stableSince = null;
699
+ let lastHash = null;
700
+ while (Date.now() - startedAt <= args.timeoutMs) {
701
+ const snapshot = await host.snapshotText({ captureFrame: true });
702
+ if (snapshot.hash === lastHash) stableSince ??= Date.now();
703
+ else {
704
+ stableSince = null;
705
+ lastHash = snapshot.hash;
706
+ }
707
+ if (stableSince !== null && Date.now() - stableSince >= args.quietMs) return {
708
+ stable: true,
709
+ ...snapshot
710
+ };
711
+ await sleep(args.intervalMs);
712
+ }
713
+ return {
714
+ stable: false,
715
+ ...await host.snapshotText({ captureFrame: true })
716
+ };
717
+ }
718
+ //#endregion
592
719
  //#region src/session/terminal_session.ts
593
720
  const ESC = "\x1B";
594
721
  var TerminalSession = class {
@@ -688,23 +815,7 @@ var TerminalSession = class {
688
815
  }
689
816
  async snapshotText(options) {
690
817
  await this.flush();
691
- if (options?.maxLines !== void 0 && options.tailLines !== void 0) throw new Error("snapshotText: maxLines and tailLines are mutually exclusive");
692
- let lines = snapshotLines(this.terminal, {
693
- scope: options?.scope,
694
- trimRight: options?.trimRight
695
- });
696
- if (options?.trimBottom ?? true) lines = trimBottomEmptyLines(lines);
697
- if (options?.maxLines !== void 0) {
698
- const max = Math.max(0, Math.trunc(options.maxLines));
699
- lines = lines.slice(0, max);
700
- }
701
- if (options?.tailLines !== void 0) {
702
- const tail = Math.max(0, Math.trunc(options.tailLines));
703
- lines = lines.slice(Math.max(0, lines.length - tail));
704
- }
705
- lines = applyTextMaskRules(lines, options?.mask);
706
- const text = lines.join("\n");
707
- const hash = fnv1a32(text);
818
+ const { text, hash } = snapshotSessionText(this.terminal, options);
708
819
  if (options?.captureFrame ?? true) this.captureFrame(text, hash);
709
820
  return {
710
821
  text,
@@ -713,36 +824,7 @@ var TerminalSession = class {
713
824
  }
714
825
  async snapshotAnsi(options) {
715
826
  await this.flush();
716
- if (options?.maxLines !== void 0 && options.tailLines !== void 0) throw new Error("snapshotAnsi: maxLines and tailLines are mutually exclusive");
717
- let lines = renderAnsiLines(this.terminal, {
718
- scope: options?.scope,
719
- trimRight: options?.trimRight
720
- });
721
- if (options?.trimBottom ?? true) lines = trimBottomEmptyAnsiLines(lines);
722
- if (options?.maxLines !== void 0) {
723
- const max = Math.max(0, Math.trunc(options.maxLines));
724
- lines = lines.slice(0, max);
725
- }
726
- if (options?.tailLines !== void 0) {
727
- const tail = Math.max(0, Math.trunc(options.tailLines));
728
- lines = lines.slice(Math.max(0, lines.length - tail));
729
- }
730
- if (options?.mask && options.mask.length > 0) {
731
- const maskedPlain = applyTextMaskRules(lines.map((line) => line.plain), options.mask);
732
- const maskedAnsi = applyTextMaskRules(lines.map((line) => line.ansi), options.mask);
733
- lines = lines.map((line, idx) => ({
734
- ...line,
735
- plain: maskedPlain[idx] ?? "",
736
- ansi: maskedAnsi[idx] ?? ""
737
- }));
738
- }
739
- const ansi = lines.map((l) => l.ansi).join("\n");
740
- return {
741
- ansi,
742
- plain: lines.map((l) => l.plain).join("\n"),
743
- hash: fnv1a32(ansi),
744
- lines
745
- };
827
+ return snapshotSessionAnsi(this.terminal, options);
746
828
  }
747
829
  async snapshotGrid(options) {
748
830
  await this.flush();
@@ -771,57 +853,10 @@ var TerminalSession = class {
771
853
  return [...this.rawOutputRing];
772
854
  }
773
855
  async waitForText(args) {
774
- const startedAt = Date.now();
775
- let closedSince = null;
776
- while (Date.now() - startedAt <= args.timeoutMs) {
777
- const snapshot = await this.snapshotText({
778
- captureFrame: true,
779
- scope: args.scope
780
- });
781
- if (args.text && snapshot.text.includes(args.text)) return {
782
- found: true,
783
- ...snapshot
784
- };
785
- if (args.regex && args.regex.test(snapshot.text)) return {
786
- found: true,
787
- ...snapshot
788
- };
789
- if (this.isClosed()) {
790
- closedSince ??= Date.now();
791
- const drainMs = Math.max(500, args.intervalMs * 4);
792
- if (Date.now() - closedSince >= drainMs) break;
793
- }
794
- await sleep(args.intervalMs);
795
- }
796
- return {
797
- found: false,
798
- ...await this.snapshotText({
799
- captureFrame: true,
800
- scope: args.scope
801
- })
802
- };
856
+ return waitForSessionText(this, args);
803
857
  }
804
858
  async waitForStableScreen(args) {
805
- const startedAt = Date.now();
806
- let stableSince = null;
807
- let lastHash = null;
808
- while (Date.now() - startedAt <= args.timeoutMs) {
809
- const snapshot = await this.snapshotText({ captureFrame: true });
810
- if (snapshot.hash === lastHash) stableSince ??= Date.now();
811
- else {
812
- stableSince = null;
813
- lastHash = snapshot.hash;
814
- }
815
- if (stableSince !== null && Date.now() - stableSince >= args.quietMs) return {
816
- stable: true,
817
- ...snapshot
818
- };
819
- await sleep(args.intervalMs);
820
- }
821
- return {
822
- stable: false,
823
- ...await this.snapshotText({ captureFrame: true })
824
- };
859
+ return waitForStableSessionScreen(this, args);
825
860
  }
826
861
  close() {
827
862
  if (this.closed === null) this.closed = { type: "closed_by_user" };
@@ -875,19 +910,5 @@ var TerminalSession = class {
875
910
  return true;
876
911
  }
877
912
  };
878
- function trimBottomEmptyLines(lines) {
879
- let end = lines.length;
880
- while (end > 0 && lines[end - 1] === "") end -= 1;
881
- return end === lines.length ? lines : lines.slice(0, end);
882
- }
883
- function trimBottomEmptyAnsiLines(lines) {
884
- let end = lines.length;
885
- while (end > 0) {
886
- const line = lines[end - 1];
887
- if (!(!line?.hasStyle && (line?.plain ?? "").trim() === "")) break;
888
- end -= 1;
889
- }
890
- return end === lines.length ? lines : lines.slice(0, end);
891
- }
892
913
  //#endregion
893
- export { applyTextMaskRules as a, encodeSgrMouse as c, isDefaultStyle as d, styleKey as f, fnv1a32 as i, extractStyle as l, TraceRecorder as n, snapshotGrid as o, encodeKey as p, sleep as r, snapshotLines as s, TerminalSession as t, findMeaningfulEndCol as u };
914
+ export { fnv1a32 as a, extractStyle as c, styleKey as d, encodeSgrMouse as f, TraceRecorder as i, findMeaningfulEndCol as l, sleep as n, snapshotGrid as o, encodeKey as p, applyTextMaskRules as r, snapshotLines as s, TerminalSession as t, isDefaultStyle as u };