@alchemy.run/sigil 0.0.0-alpha.4 → 0.0.0-alpha.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -9
- package/THIRD_PARTY_NOTICES.md +39 -1
- package/dist/Text-BobFKi74.d.ts +452 -0
- package/dist/ansi.d.ts +122 -131
- package/dist/ansi.js +86 -6
- package/dist/capabilities.d.ts +5 -0
- package/dist/capabilities.js +3 -0
- package/dist/cell-_ZVhbfl0.js +44 -0
- package/dist/color-CkbalRqK.js +2 -0
- package/dist/color-policy-BMzMwV7Q.d.ts +22 -0
- package/dist/color-policy-SVj1pYTA.js +560 -0
- package/dist/color-profile-DHhQHY55.js +36 -0
- package/dist/color-profile-u0Nhe9Nv.d.ts +97 -0
- package/dist/color.d.ts +21 -0
- package/dist/color.js +3 -0
- package/dist/cursor-position-D2LAkRG0.d.ts +7 -0
- package/dist/detect-Bh4yGP6w.d.ts +186 -0
- package/dist/detect-BuTXtY6e.js +373 -0
- package/dist/env-YVw64yZS.js +9 -0
- package/dist/escapes-CB_6CWOE.d.ts +72 -0
- package/dist/geometry-BxXOzJgo.d.ts +11 -0
- package/dist/index-DZ88EXJv.d.ts +21 -0
- package/dist/index.d.ts +143 -498
- package/dist/index.js +1026 -2244
- package/dist/osc-CCH7xDoS.js +71 -0
- package/dist/osc-Cn0fw77g.d.ts +23 -0
- package/dist/paint-C19minOS.d.ts +81 -0
- package/dist/query-vaIeGOkH.d.ts +152 -0
- package/dist/router.d.ts +392 -0
- package/dist/router.js +709 -0
- package/dist/sample-Cqw1bjUL.js +445 -0
- package/dist/screen-BOSLQ8fF.d.ts +49 -0
- package/dist/screen-CiPytswf.js +342 -0
- package/dist/screen.d.ts +5 -0
- package/dist/screen.js +5 -0
- package/dist/semantic-text-style-DIMzC7xt.js +91 -0
- package/dist/serialize-BTkAZgw1.js +79 -0
- package/dist/session-aZr9O8h3.js +665 -0
- package/dist/sgr-BhwaWAJB.js +246 -0
- package/dist/store-CgrG9K4y.d.ts +72 -0
- package/dist/string-width-CijQwpIk.js +69 -0
- package/dist/strip-BvU4toXG.js +6 -0
- package/dist/terminal.d.ts +118 -0
- package/dist/terminal.js +2 -0
- package/dist/tokenize-AjqbvtiT.js +1242 -0
- package/dist/tokenize-Dx1y_l5H.d.ts +57 -0
- package/dist/truncate-D31fhU6i.js +562 -0
- package/dist/use-focus-BzqAJi0n.js +1337 -0
- package/package.json +41 -9
- package/src/ansi/chalk.ts +5 -3
- package/src/ansi/escapes.ts +14 -0
- package/src/ansi/graphemes.ts +8 -0
- package/src/ansi/hyperlink.ts +44 -0
- package/src/ansi/index.ts +3 -1
- package/src/ansi/osc.ts +77 -0
- package/src/ansi/tokenize.ts +3 -4
- package/src/capabilities/color-policy.ts +34 -0
- package/src/capabilities/detect.ts +594 -0
- package/src/capabilities/index.ts +37 -0
- package/src/capabilities/query.ts +657 -0
- package/src/capabilities/store.ts +379 -0
- package/src/color/index.ts +3 -0
- package/src/color/paint.ts +169 -0
- package/src/color/palette.ts +48 -0
- package/src/color/sample.ts +323 -0
- package/src/color.ts +1 -0
- package/src/components/AnsiText.tsx +42 -0
- package/src/components/App.tsx +98 -10
- package/src/components/BackgroundContext.ts +2 -3
- package/src/components/Box.tsx +0 -8
- package/src/components/CursorContext.ts +1 -1
- package/src/components/Hyperlink.tsx +56 -0
- package/src/components/TerminalOscContext.ts +25 -0
- package/src/components/Text.tsx +22 -45
- package/src/components/Transform.tsx +1 -1
- package/src/dom.ts +11 -2
- package/src/global.d.ts +3 -0
- package/src/hooks/use-capabilities.ts +73 -0
- package/src/hooks/use-cursor.ts +2 -2
- package/src/hooks/use-terminal-osc.ts +59 -0
- package/src/index.ts +45 -1
- package/src/ink.tsx +213 -153
- package/src/{render-node-to-output.ts → paint-tree.ts} +75 -48
- package/src/reconciler.ts +24 -3
- package/src/render-background.ts +36 -15
- package/src/render-border.ts +94 -61
- package/src/render-frame.ts +83 -0
- package/src/render-to-string.ts +21 -6
- package/src/render.ts +15 -6
- package/src/router/components.tsx +343 -0
- package/src/router/context.ts +41 -0
- package/src/router/history.ts +194 -0
- package/src/router/hooks.tsx +391 -0
- package/src/router/index.ts +34 -0
- package/src/router/matcher.ts +571 -0
- package/src/screen/ansi.ts +184 -0
- package/src/screen/canvas.ts +160 -0
- package/src/screen/cell.ts +138 -0
- package/src/screen/color-profile.ts +47 -0
- package/src/screen/geometry.ts +9 -0
- package/src/screen/index.ts +6 -0
- package/src/screen/screen.ts +305 -0
- package/src/screen/serialize.ts +129 -0
- package/src/screen.ts +1 -0
- package/src/semantic-text-style.ts +118 -0
- package/src/squash-text-nodes.ts +2 -5
- package/src/structured-text.ts +325 -0
- package/src/styles.ts +19 -14
- package/src/terminal/index.ts +2 -0
- package/src/terminal/inline-presenter.ts +120 -0
- package/src/terminal/input.ts +86 -0
- package/src/terminal/render-scheduler.ts +37 -0
- package/src/terminal/screen-presenter.ts +188 -0
- package/src/terminal/session.ts +407 -0
- package/src/terminal.ts +1 -0
- package/src/testing/browser.ts +588 -0
- package/src/testing/emulators.ts +205 -0
- package/src/testing/explorer-app/index.html +12 -0
- package/src/testing/explorer-app/main.ts +381 -0
- package/src/testing/explorer-app/style.css +194 -0
- package/src/testing/explorer-app/tsconfig.json +15 -0
- package/src/testing/explorer-app/vite-env.d.ts +1 -0
- package/src/testing/index.ts +26 -0
- package/src/testing/keys.ts +56 -0
- package/src/testing/live.ts +85 -0
- package/src/testing/matchers.ts +70 -0
- package/src/testing/public.ts +94 -0
- package/src/testing/terminal.ts +349 -0
- package/src/testing/vitest.ts +157 -0
- package/src/transform-adapter.ts +14 -0
- package/src/wrap-text.ts +4 -0
- package/dist/sgr-CMfEpjSk.d.ts +0 -91
- package/dist/truncate-Cr6xVFMa.js +0 -2330
- package/src/ansi/supports-color.ts +0 -207
- package/src/colorize.ts +0 -60
- package/src/log-update.ts +0 -370
- package/src/output.ts +0 -308
- package/src/renderer.ts +0 -73
package/src/ink.tsx
CHANGED
|
@@ -7,11 +7,13 @@ import { type FiberRoot } from "react-reconciler";
|
|
|
7
7
|
import { LegacyRoot, ConcurrentRoot } from "react-reconciler/constants.js";
|
|
8
8
|
|
|
9
9
|
import { ansiEscapes, bsu, esu } from "#/ansi/escapes.ts";
|
|
10
|
+
import type { ClipboardSelection, TerminalProgressState } from "#/ansi/osc.ts";
|
|
10
11
|
import { wrapAnsi } from "#/ansi/wrap.ts";
|
|
11
12
|
import { accessibilityContext as AccessibilityContext } from "#/components/AccessibilityContext.ts";
|
|
12
13
|
import { App } from "#/components/App.tsx";
|
|
13
14
|
import { type TerminalSuspension } from "#/components/AppContext.ts";
|
|
14
|
-
import {
|
|
15
|
+
import { TerminalOscContext } from "#/components/TerminalOscContext.ts";
|
|
16
|
+
import type { CursorPosition } from "#/cursor-position.ts";
|
|
15
17
|
import * as dom from "#/dom.ts";
|
|
16
18
|
import { isSigilDev, isInCi, isScreenReader, isTty, isWindows } from "#/env.ts";
|
|
17
19
|
import { instances } from "#/instances.ts";
|
|
@@ -21,17 +23,46 @@ import {
|
|
|
21
23
|
resolveFlags,
|
|
22
24
|
detectKittySupport,
|
|
23
25
|
} from "#/kitty-keyboard.ts";
|
|
24
|
-
import { logUpdate, type LogUpdate, type CursorPosition } from "#/log-update.ts";
|
|
25
26
|
import { patchConsole, patchStreamWrite } from "#/patch-console.ts";
|
|
26
27
|
import { reconciler } from "#/reconciler.ts";
|
|
27
|
-
import {
|
|
28
|
+
import { renderFrame } from "#/render-frame.ts";
|
|
29
|
+
import { Screen } from "#/screen/screen.ts";
|
|
28
30
|
import { signalExit } from "#/signal-exit.ts";
|
|
29
31
|
import { type OutputStream } from "#/stream.ts";
|
|
30
|
-
import {
|
|
32
|
+
import { createInlinePresenter } from "#/terminal/inline-presenter.ts";
|
|
33
|
+
import { createRenderScheduler } from "#/terminal/render-scheduler.ts";
|
|
34
|
+
import { TerminalSession } from "#/terminal/session.ts";
|
|
35
|
+
import { type Throttled } from "#/throttle.ts";
|
|
31
36
|
import { getWindowSize } from "#/utils.ts";
|
|
32
37
|
import { Yoga } from "#/yoga/index.ts";
|
|
33
38
|
|
|
34
39
|
const noop = () => {};
|
|
40
|
+
const beforeExitCallbacks = new Set<() => void>();
|
|
41
|
+
const runBeforeExitCallbacks = (): void => {
|
|
42
|
+
for (const callback of beforeExitCallbacks) callback();
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
function registerBeforeExit(callback: () => void): () => void {
|
|
46
|
+
if (beforeExitCallbacks.size === 0) process.on("beforeExit", runBeforeExitCallbacks);
|
|
47
|
+
beforeExitCallbacks.add(callback);
|
|
48
|
+
return () => {
|
|
49
|
+
beforeExitCallbacks.delete(callback);
|
|
50
|
+
if (beforeExitCallbacks.size === 0) process.off("beforeExit", runBeforeExitCallbacks);
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function bottomRows(screen: Screen, height: number): Screen {
|
|
55
|
+
if (screen.height <= height) return screen;
|
|
56
|
+
const cropped = new Screen(screen.width, height);
|
|
57
|
+
const offset = screen.height - height;
|
|
58
|
+
for (let y = 0; y < height; y++) {
|
|
59
|
+
for (let x = 0; x < screen.width; x++) {
|
|
60
|
+
const cell = screen.cellAt(x, y + offset);
|
|
61
|
+
if (cell && cell.width > 0) cropped.setCell(x, y, cell);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return cropped;
|
|
65
|
+
}
|
|
35
66
|
|
|
36
67
|
const shouldClearTerminalForFrame = ({
|
|
37
68
|
isTTY,
|
|
@@ -187,7 +218,7 @@ export type Options = {
|
|
|
187
218
|
onRender?: (metrics: RenderMetrics) => void;
|
|
188
219
|
isScreenReaderEnabled?: boolean;
|
|
189
220
|
maxFps?: number;
|
|
190
|
-
|
|
221
|
+
colorProfile?: import("#/screen/color-profile.ts").ColorProfile;
|
|
191
222
|
|
|
192
223
|
/**
|
|
193
224
|
Enable React Concurrent Rendering mode.
|
|
@@ -241,7 +272,7 @@ export type Options = {
|
|
|
241
272
|
};
|
|
242
273
|
|
|
243
274
|
/**
|
|
244
|
-
A live
|
|
275
|
+
A live React terminal runtime for one stdout stream, created by `createInk`.
|
|
245
276
|
*/
|
|
246
277
|
export type Ink = {
|
|
247
278
|
/**
|
|
@@ -269,6 +300,12 @@ export type Ink = {
|
|
|
269
300
|
Clear output.
|
|
270
301
|
*/
|
|
271
302
|
clear: () => void;
|
|
303
|
+
|
|
304
|
+
/** Copy text through the renderer-owned terminal session. */
|
|
305
|
+
copyToClipboard: (text: string, selection?: ClipboardSelection) => boolean;
|
|
306
|
+
|
|
307
|
+
/** Update terminal-native progress through the renderer-owned session. */
|
|
308
|
+
setProgress: (state: TerminalProgressState, value?: number) => boolean;
|
|
272
309
|
};
|
|
273
310
|
|
|
274
311
|
export const createInk = (options: Options): Ink => {
|
|
@@ -297,54 +334,42 @@ export const createInk = (options: Options): Ink => {
|
|
|
297
334
|
// where the property is absent (e.g. `node app.js | cat`).
|
|
298
335
|
const interactive = options.interactive ?? (!isInCi && Boolean(options.stdout.isTTY));
|
|
299
336
|
|
|
300
|
-
|
|
337
|
+
const terminal = new TerminalSession({
|
|
338
|
+
stdin: options.stdin,
|
|
339
|
+
stdout: options.stdout,
|
|
340
|
+
stderr: options.stderr,
|
|
341
|
+
colorPolicy: options.colorProfile ?? "auto",
|
|
342
|
+
onCapabilitiesChange: () => rootNode.onRender?.(),
|
|
343
|
+
});
|
|
344
|
+
const terminalOsc = {
|
|
345
|
+
publishProgress: (
|
|
346
|
+
owner: symbol,
|
|
347
|
+
state: import("#/ansi/osc.ts").TerminalProgressState,
|
|
348
|
+
value?: number,
|
|
349
|
+
) => {
|
|
350
|
+
terminal.publishProgress(owner, state, value);
|
|
351
|
+
},
|
|
352
|
+
copyToClipboard: (text: string, selection?: import("#/ansi/osc.ts").ClipboardSelection) => {
|
|
353
|
+
terminal.copyToClipboard(text, selection);
|
|
354
|
+
},
|
|
355
|
+
publishTitle: (owner: symbol, title?: string) => terminal.publishTitle(owner, title),
|
|
356
|
+
setWorkingDirectory: (directory: URL | string) => terminal.setWorkingDirectory(directory),
|
|
357
|
+
notify: (title: string) => terminal.notify(title),
|
|
358
|
+
setPointerShape: (shape: string) => terminal.setPointerShape(shape),
|
|
359
|
+
};
|
|
360
|
+
const capabilitiesStore = terminal.capabilities;
|
|
301
361
|
|
|
302
362
|
const unthrottled = options.debug || isScreenReaderEnabled;
|
|
303
|
-
const
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
// so future changes don't accidentally reintroduce zero-delay loops.
|
|
307
|
-
const frameIntervalMs = maxFps > 0 ? Math.max(1, Math.ceil(1000 / maxFps)) : 0;
|
|
308
|
-
const renderThrottleMs = unthrottled ? 0 : frameIntervalMs;
|
|
309
|
-
|
|
310
|
-
let hasPendingThrottledRender = false;
|
|
311
|
-
let throttledOnRender: Throttled<never[]> | undefined;
|
|
312
|
-
|
|
313
|
-
if (unthrottled) {
|
|
314
|
-
rootNode.onRender = onRender;
|
|
315
|
-
} else {
|
|
316
|
-
const throttled = throttle(onRender, frameIntervalMs);
|
|
317
|
-
rootNode.onRender = () => {
|
|
318
|
-
hasPendingThrottledRender = true;
|
|
319
|
-
throttled();
|
|
320
|
-
};
|
|
321
|
-
|
|
322
|
-
throttledOnRender = throttled;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
rootNode.onImmediateRender = onRender;
|
|
326
|
-
rootNode.onStaticChange = handleStaticChange;
|
|
327
|
-
const log = logUpdate.create(options.stdout, {
|
|
328
|
-
incremental: options.incrementalRendering,
|
|
363
|
+
const renderScheduler = createRenderScheduler(onRender, {
|
|
364
|
+
unthrottled,
|
|
365
|
+
maxFps: options.maxFps ?? 30,
|
|
329
366
|
});
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
if (sync && shouldWrite) {
|
|
337
|
-
options.stdout.write(bsu);
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
log(output);
|
|
341
|
-
|
|
342
|
-
if (sync && shouldWrite) {
|
|
343
|
-
options.stdout.write(esu);
|
|
344
|
-
}
|
|
345
|
-
});
|
|
346
|
-
const throttledLog: LogUpdate | Throttled<[output: string]> = logThrottle ?? log;
|
|
347
|
-
|
|
367
|
+
rootNode.onRender = renderScheduler.schedule;
|
|
368
|
+
rootNode.onImmediateRender = renderScheduler.immediate;
|
|
369
|
+
rootNode.onStaticChange = handleStaticChange;
|
|
370
|
+
const accessiblePresenter = isScreenReaderEnabled
|
|
371
|
+
? createInlinePresenter(options.stdout, { showCursor: true })
|
|
372
|
+
: undefined;
|
|
348
373
|
// Ignore last render after unmounting a tree to prevent empty output before exit
|
|
349
374
|
let isUnmounted = false;
|
|
350
375
|
let isUnmounting = false;
|
|
@@ -355,6 +380,7 @@ export const createInk = (options: Options): Ink => {
|
|
|
355
380
|
let lastOutput = "";
|
|
356
381
|
let lastOutputToRender = "";
|
|
357
382
|
let lastOutputHeight = 0;
|
|
383
|
+
let lastScreen: Screen | undefined;
|
|
358
384
|
let lastTerminalWidth = getWindowSize(options.stdout).columns;
|
|
359
385
|
let lastTerminalHeight = getWindowSize(options.stdout).rows;
|
|
360
386
|
|
|
@@ -363,7 +389,7 @@ export const createInk = (options: Options): Ink => {
|
|
|
363
389
|
let fullStaticOutput = "";
|
|
364
390
|
|
|
365
391
|
let exitResult: unknown;
|
|
366
|
-
let
|
|
392
|
+
let unsubscribeBeforeExit: (() => void) | undefined;
|
|
367
393
|
let restoreConsole: (() => void) | undefined;
|
|
368
394
|
// Partial trailing lines from captured direct writes, held until a newline.
|
|
369
395
|
const capturedStdioTails = { stdout: "", stderr: "" };
|
|
@@ -372,8 +398,6 @@ export const createInk = (options: Options): Ink => {
|
|
|
372
398
|
let kittyFlags: KittyFlagName[] | undefined;
|
|
373
399
|
let cancelKittyDetection: (() => void) | undefined;
|
|
374
400
|
let nextRenderCommit: { promise: Promise<void>; resolve: () => void } | undefined;
|
|
375
|
-
// Set while suspendTerminal() has handed the terminal to a child process.
|
|
376
|
-
let isSuspended = false;
|
|
377
401
|
// Input pause/resume hooks registered by the App component, which owns raw
|
|
378
402
|
// mode and bracketed paste state.
|
|
379
403
|
let pauseInput: (() => void) | undefined;
|
|
@@ -436,11 +460,12 @@ export const createInk = (options: Options): Ink => {
|
|
|
436
460
|
// render to be a full rewrite instead of an incremental diff that
|
|
437
461
|
// would skip "unchanged" lines over stale screen content.
|
|
438
462
|
if (currentWidth < lastTerminalWidth || currentHeight !== lastTerminalHeight) {
|
|
439
|
-
//
|
|
463
|
+
// Clearing erases the full previous frame line count from
|
|
440
464
|
// the cursor upward — after a height grow that also covers frame
|
|
441
465
|
// lines the emulator pulled back from scrollback, so no extra
|
|
442
466
|
// erase is needed for them.
|
|
443
|
-
|
|
467
|
+
clearLiveOutput();
|
|
468
|
+
resetLiveOutput();
|
|
444
469
|
lastOutput = "";
|
|
445
470
|
lastOutputToRender = "";
|
|
446
471
|
// Also forget the previous frame height: it described a frame
|
|
@@ -473,8 +498,8 @@ export const createInk = (options: Options): Ink => {
|
|
|
473
498
|
}
|
|
474
499
|
|
|
475
500
|
function setCursorPosition(position: CursorPosition | undefined): void {
|
|
476
|
-
|
|
477
|
-
|
|
501
|
+
terminal.setCursor(position);
|
|
502
|
+
accessiblePresenter?.setCursorPosition(position);
|
|
478
503
|
}
|
|
479
504
|
|
|
480
505
|
function restoreLastOutput(): void {
|
|
@@ -482,10 +507,31 @@ export const createInk = (options: Options): Ink => {
|
|
|
482
507
|
return;
|
|
483
508
|
}
|
|
484
509
|
|
|
485
|
-
//
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
510
|
+
// Replay the latest cursor intent when restoring after external output.
|
|
511
|
+
if (isScreenReaderEnabled) {
|
|
512
|
+
accessiblePresenter!.setCursorPosition(terminal.cursor.position);
|
|
513
|
+
accessiblePresenter!(lastOutputToRender || lastOutput + "\n");
|
|
514
|
+
} else if (lastScreen) {
|
|
515
|
+
terminal.present(lastScreen, {
|
|
516
|
+
fullscreen: lastOutputToRender === lastOutput,
|
|
517
|
+
forceRewrite: true,
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function clearLiveOutput(): void {
|
|
523
|
+
if (isScreenReaderEnabled) accessiblePresenter!.clear();
|
|
524
|
+
else terminal.clearFrame();
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function finishLiveOutput(): void {
|
|
528
|
+
if (isScreenReaderEnabled) accessiblePresenter!.done();
|
|
529
|
+
else terminal.finishFrame();
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function resetLiveOutput(): void {
|
|
533
|
+
if (isScreenReaderEnabled) accessiblePresenter!.reset();
|
|
534
|
+
else terminal.resetFrame();
|
|
489
535
|
}
|
|
490
536
|
|
|
491
537
|
function calculateLayout(): void {
|
|
@@ -502,7 +548,7 @@ export const createInk = (options: Options): Ink => {
|
|
|
502
548
|
}
|
|
503
549
|
|
|
504
550
|
function onRender(): void {
|
|
505
|
-
|
|
551
|
+
renderScheduler.markRendered();
|
|
506
552
|
|
|
507
553
|
if (isUnmounted) {
|
|
508
554
|
return;
|
|
@@ -511,7 +557,7 @@ export const createInk = (options: Options): Ink => {
|
|
|
511
557
|
// While suspended, the terminal belongs to a child process. Discard queued
|
|
512
558
|
// renders; resume() forces a full redraw once Ink reclaims the terminal.
|
|
513
559
|
// Resolve any awaited render commit so callers don't hang during suspension.
|
|
514
|
-
if (
|
|
560
|
+
if (terminal.suspended) {
|
|
515
561
|
if (nextRenderCommit) {
|
|
516
562
|
nextRenderCommit.resolve();
|
|
517
563
|
nextRenderCommit = undefined;
|
|
@@ -526,7 +572,25 @@ export const createInk = (options: Options): Ink => {
|
|
|
526
572
|
}
|
|
527
573
|
|
|
528
574
|
const startTime = performance.now();
|
|
529
|
-
const
|
|
575
|
+
const rendered = renderFrame(rootNode, isScreenReaderEnabled, {
|
|
576
|
+
colorProfile: terminal.colorProfile,
|
|
577
|
+
paintContext: {
|
|
578
|
+
appearance: capabilitiesStore.current.theme.appearance,
|
|
579
|
+
palette: capabilitiesStore.current.theme.palette,
|
|
580
|
+
},
|
|
581
|
+
});
|
|
582
|
+
const screen = rendered.screen;
|
|
583
|
+
const output = rendered.accessibleText ?? (screen ? terminal.encode(screen) : "");
|
|
584
|
+
const outputHeight =
|
|
585
|
+
rendered.accessibleText === undefined
|
|
586
|
+
? (screen?.height ?? 0)
|
|
587
|
+
: rendered.accessibleText === ""
|
|
588
|
+
? 0
|
|
589
|
+
: rendered.accessibleText.split("\n").length;
|
|
590
|
+
const staticBody =
|
|
591
|
+
rendered.staticAccessibleText ??
|
|
592
|
+
(rendered.staticScreen ? terminal.encode(rendered.staticScreen) : "");
|
|
593
|
+
const staticOutput = staticBody ? `${staticBody}\n` : "";
|
|
530
594
|
|
|
531
595
|
options.onRender?.({ renderTime: performance.now() - startTime });
|
|
532
596
|
|
|
@@ -608,29 +672,38 @@ export const createInk = (options: Options): Ink => {
|
|
|
608
672
|
fullStaticOutput += staticOutput;
|
|
609
673
|
}
|
|
610
674
|
|
|
611
|
-
renderInteractiveFrame(
|
|
675
|
+
renderInteractiveFrame(
|
|
676
|
+
output,
|
|
677
|
+
outputHeight,
|
|
678
|
+
hasStaticOutput ? staticOutput : "",
|
|
679
|
+
screen,
|
|
680
|
+
false,
|
|
681
|
+
);
|
|
612
682
|
}
|
|
613
683
|
|
|
614
684
|
function render(node: ReactNode): void {
|
|
615
685
|
const tree = (
|
|
616
686
|
<AccessibilityContext.Provider value={{ isScreenReaderEnabled }}>
|
|
617
|
-
<
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
687
|
+
<TerminalOscContext.Provider value={terminalOsc}>
|
|
688
|
+
<App
|
|
689
|
+
stdin={options.stdin}
|
|
690
|
+
stdout={options.stdout}
|
|
691
|
+
stderr={options.stderr}
|
|
692
|
+
exitOnCtrlC={options.exitOnCtrlC}
|
|
693
|
+
interactive={interactive}
|
|
694
|
+
renderThrottleMs={renderScheduler.intervalMs}
|
|
695
|
+
terminalInput={terminal.input}
|
|
696
|
+
writeToStdout={writeToStdout}
|
|
697
|
+
writeToStderr={writeToStderr}
|
|
698
|
+
setCursorPosition={setCursorPosition}
|
|
699
|
+
onExit={handleAppExit}
|
|
700
|
+
onWaitUntilRenderFlush={waitUntilRenderFlush}
|
|
701
|
+
onSuspendTerminal={suspendTerminal}
|
|
702
|
+
onRegisterInputControl={registerInputControl}
|
|
703
|
+
>
|
|
704
|
+
{node}
|
|
705
|
+
</App>
|
|
706
|
+
</TerminalOscContext.Provider>
|
|
634
707
|
</AccessibilityContext.Provider>
|
|
635
708
|
);
|
|
636
709
|
|
|
@@ -652,7 +725,7 @@ export const createInk = (options: Options): Ink => {
|
|
|
652
725
|
// While suspended, the terminal belongs to a child process. Don't erase or
|
|
653
726
|
// repaint Ink's frame around console output; the forced redraw on resume
|
|
654
727
|
// restores the screen.
|
|
655
|
-
if (
|
|
728
|
+
if (terminal.suspended) {
|
|
656
729
|
return;
|
|
657
730
|
}
|
|
658
731
|
|
|
@@ -671,7 +744,7 @@ export const createInk = (options: Options): Ink => {
|
|
|
671
744
|
options.stdout.write(bsu);
|
|
672
745
|
}
|
|
673
746
|
|
|
674
|
-
|
|
747
|
+
clearLiveOutput();
|
|
675
748
|
options.stdout.write(data);
|
|
676
749
|
restoreLastOutput();
|
|
677
750
|
|
|
@@ -686,7 +759,7 @@ export const createInk = (options: Options): Ink => {
|
|
|
686
759
|
}
|
|
687
760
|
|
|
688
761
|
// See writeToStdout: stay off the terminal while suspended.
|
|
689
|
-
if (
|
|
762
|
+
if (terminal.suspended) {
|
|
690
763
|
return;
|
|
691
764
|
}
|
|
692
765
|
|
|
@@ -706,7 +779,7 @@ export const createInk = (options: Options): Ink => {
|
|
|
706
779
|
options.stdout.write(bsu);
|
|
707
780
|
}
|
|
708
781
|
|
|
709
|
-
|
|
782
|
+
clearLiveOutput();
|
|
710
783
|
options.stderr.write(data);
|
|
711
784
|
restoreLastOutput();
|
|
712
785
|
|
|
@@ -723,10 +796,8 @@ export const createInk = (options: Options): Ink => {
|
|
|
723
796
|
|
|
724
797
|
isUnmounting = true;
|
|
725
798
|
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
beforeExitHandler = undefined;
|
|
729
|
-
}
|
|
799
|
+
unsubscribeBeforeExit?.();
|
|
800
|
+
unsubscribeBeforeExit = undefined;
|
|
730
801
|
|
|
731
802
|
const { canWriteToStdout } = getWritableStreamState(options.stdout);
|
|
732
803
|
|
|
@@ -737,14 +808,14 @@ export const createInk = (options: Options): Ink => {
|
|
|
737
808
|
|
|
738
809
|
// Clear any pending throttled render timer on unmount. When stdout is writable,
|
|
739
810
|
// flush so the final frame is emitted; otherwise cancel to avoid delayed callbacks.
|
|
740
|
-
settleThrottle(
|
|
811
|
+
settleThrottle(renderScheduler.throttled, canWriteToStdout);
|
|
741
812
|
|
|
742
813
|
if (canWriteToStdout) {
|
|
743
814
|
// If throttling is enabled and there is already a pending render, flushing above
|
|
744
815
|
// is sufficient. Also avoid calling onRender() again when static output already
|
|
745
816
|
// exists, as that can duplicate <Static> children output on exit (see issue #397).
|
|
746
817
|
const shouldRenderFinalFrame =
|
|
747
|
-
!
|
|
818
|
+
!renderScheduler.throttled || (!renderScheduler.pending && fullStaticOutput === "");
|
|
748
819
|
|
|
749
820
|
if (shouldRenderFinalFrame) {
|
|
750
821
|
calculateLayout();
|
|
@@ -757,10 +828,8 @@ export const createInk = (options: Options): Ink => {
|
|
|
757
828
|
isUnmounted = true;
|
|
758
829
|
|
|
759
830
|
unsubscribeExit();
|
|
831
|
+
terminal.cleanup();
|
|
760
832
|
|
|
761
|
-
// Flush any pending throttled log writes if possible, otherwise cancel to
|
|
762
|
-
// prevent delayed callbacks from writing to a closed stream.
|
|
763
|
-
settleThrottle(logThrottle, canWriteToStdout);
|
|
764
833
|
if (typeof restoreConsole === "function") {
|
|
765
834
|
// Once unmount starts, Ink stops trying to manage teardown-time
|
|
766
835
|
// console output. Restoring the native console before React cleanup keeps
|
|
@@ -790,10 +859,9 @@ export const createInk = (options: Options): Ink => {
|
|
|
790
859
|
// diagnostics onto it. Trying to preserve teardown output across the
|
|
791
860
|
// buffer switch adds fragile lifecycle-specific behavior, so Ink keeps
|
|
792
861
|
// alternate-screen teardown intentionally simple and best-effort.
|
|
793
|
-
if (alternateScreen) {
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
alternateScreen = false;
|
|
862
|
+
if (terminal.alternateScreen) {
|
|
863
|
+
terminal.setAlternateScreen(false);
|
|
864
|
+
terminal.setCursorAppearance({ visible: true });
|
|
797
865
|
}
|
|
798
866
|
|
|
799
867
|
if (!interactive) {
|
|
@@ -803,7 +871,7 @@ export const createInk = (options: Options): Ink => {
|
|
|
803
871
|
// deferred during rendering).
|
|
804
872
|
options.stdout.write(options.debug ? "\n" : lastOutput + "\n");
|
|
805
873
|
} else if (!options.debug) {
|
|
806
|
-
|
|
874
|
+
finishLiveOutput();
|
|
807
875
|
}
|
|
808
876
|
}
|
|
809
877
|
|
|
@@ -857,12 +925,10 @@ export const createInk = (options: Options): Ink => {
|
|
|
857
925
|
}
|
|
858
926
|
|
|
859
927
|
async function waitUntilExit(): Promise<unknown> {
|
|
860
|
-
if (!
|
|
861
|
-
|
|
928
|
+
if (!unsubscribeBeforeExit) {
|
|
929
|
+
unsubscribeBeforeExit = registerBeforeExit(() => {
|
|
862
930
|
unmount();
|
|
863
|
-
};
|
|
864
|
-
|
|
865
|
-
process.once("beforeExit", beforeExitHandler);
|
|
931
|
+
});
|
|
866
932
|
}
|
|
867
933
|
|
|
868
934
|
return exitPromise;
|
|
@@ -899,9 +965,8 @@ export const createInk = (options: Options): Ink => {
|
|
|
899
965
|
|
|
900
966
|
const { canWriteToStdout } = getWritableStreamState(options.stdout);
|
|
901
967
|
|
|
902
|
-
// Flush pending
|
|
903
|
-
settleThrottle(
|
|
904
|
-
settleThrottle(logThrottle, canWriteToStdout);
|
|
968
|
+
// Flush pending scheduled rendering so its output is included in this wait.
|
|
969
|
+
settleThrottle(renderScheduler.throttled, canWriteToStdout);
|
|
905
970
|
|
|
906
971
|
if (canWriteToStdout) {
|
|
907
972
|
await new Promise<void>((resolve) => {
|
|
@@ -917,10 +982,10 @@ export const createInk = (options: Options): Ink => {
|
|
|
917
982
|
|
|
918
983
|
function clear(): void {
|
|
919
984
|
if (interactive && !options.debug) {
|
|
920
|
-
|
|
985
|
+
clearLiveOutput();
|
|
921
986
|
// Sync lastOutput so that unmount's final onRender
|
|
922
|
-
// sees it as unchanged and
|
|
923
|
-
|
|
987
|
+
// sees it as unchanged and the presenter skips it
|
|
988
|
+
if (isScreenReaderEnabled) accessiblePresenter!.sync(lastOutputToRender || lastOutput + "\n");
|
|
924
989
|
}
|
|
925
990
|
}
|
|
926
991
|
|
|
@@ -1049,12 +1114,9 @@ export const createInk = (options: Options): Ink => {
|
|
|
1049
1114
|
}
|
|
1050
1115
|
|
|
1051
1116
|
function setAlternateScreen(enabled: boolean): void {
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
writeBestEffort(options.stdout, ansiEscapes.enterAlternativeScreen);
|
|
1056
|
-
writeBestEffort(options.stdout, hideCursorEscape);
|
|
1057
|
-
}
|
|
1117
|
+
terminal.setAlternateScreen(enabled && interactive && Boolean(options.stdout.isTTY), {
|
|
1118
|
+
hideCursor: true,
|
|
1119
|
+
});
|
|
1058
1120
|
}
|
|
1059
1121
|
|
|
1060
1122
|
function shouldSync(): boolean {
|
|
@@ -1092,7 +1154,10 @@ export const createInk = (options: Options): Ink => {
|
|
|
1092
1154
|
output: string,
|
|
1093
1155
|
outputHeight: number,
|
|
1094
1156
|
staticOutput: string,
|
|
1157
|
+
screen: Screen | undefined,
|
|
1158
|
+
hasOverflow: boolean,
|
|
1095
1159
|
): void {
|
|
1160
|
+
if (!screen) return;
|
|
1096
1161
|
const hasStaticOutput = staticOutput !== "";
|
|
1097
1162
|
const isTTY = Boolean(options.stdout.isTTY);
|
|
1098
1163
|
|
|
@@ -1110,6 +1175,7 @@ export const createInk = (options: Options): Ink => {
|
|
|
1110
1175
|
const lines = output.split("\n");
|
|
1111
1176
|
output = lines.slice(lines.length - viewportRows).join("\n");
|
|
1112
1177
|
outputHeight = viewportRows;
|
|
1178
|
+
screen = bottomRows(screen, viewportRows);
|
|
1113
1179
|
}
|
|
1114
1180
|
|
|
1115
1181
|
const isFullscreen = isTTY && outputHeight >= viewportRows;
|
|
@@ -1133,7 +1199,8 @@ export const createInk = (options: Options): Ink => {
|
|
|
1133
1199
|
lastOutput = output;
|
|
1134
1200
|
lastOutputToRender = outputToRender;
|
|
1135
1201
|
lastOutputHeight = outputHeight;
|
|
1136
|
-
|
|
1202
|
+
lastScreen = screen;
|
|
1203
|
+
terminal.resetFrame();
|
|
1137
1204
|
|
|
1138
1205
|
if (sync) {
|
|
1139
1206
|
options.stdout.write(esu);
|
|
@@ -1142,28 +1209,26 @@ export const createInk = (options: Options): Ink => {
|
|
|
1142
1209
|
return;
|
|
1143
1210
|
}
|
|
1144
1211
|
|
|
1145
|
-
|
|
1212
|
+
const willPresent = terminal.willPresent(screen, {
|
|
1213
|
+
fullscreen: isFullscreen,
|
|
1214
|
+
forceRewrite: hasStaticOutput || hasOverflow,
|
|
1215
|
+
});
|
|
1216
|
+
const sync = shouldSync() && willPresent;
|
|
1217
|
+
if (sync) terminal.write(bsu);
|
|
1146
1218
|
if (hasStaticOutput) {
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
options.stdout.write(bsu);
|
|
1150
|
-
}
|
|
1151
|
-
|
|
1152
|
-
log.clear();
|
|
1153
|
-
options.stdout.write(staticOutput);
|
|
1154
|
-
log(outputToRender);
|
|
1155
|
-
|
|
1156
|
-
if (sync) {
|
|
1157
|
-
options.stdout.write(esu);
|
|
1158
|
-
}
|
|
1159
|
-
} else if (output !== lastOutput || log.isCursorDirty()) {
|
|
1160
|
-
// ThrottledLog manages its own bsu/esu at actual write time
|
|
1161
|
-
throttledLog(outputToRender);
|
|
1219
|
+
terminal.clearFrame();
|
|
1220
|
+
terminal.write(staticOutput);
|
|
1162
1221
|
}
|
|
1222
|
+
terminal.present(screen, {
|
|
1223
|
+
fullscreen: isFullscreen,
|
|
1224
|
+
forceRewrite: hasStaticOutput || hasOverflow,
|
|
1225
|
+
});
|
|
1226
|
+
if (sync) terminal.write(esu);
|
|
1163
1227
|
|
|
1164
1228
|
lastOutput = output;
|
|
1165
1229
|
lastOutputToRender = outputToRender;
|
|
1166
1230
|
lastOutputHeight = outputHeight;
|
|
1231
|
+
lastScreen = screen;
|
|
1167
1232
|
}
|
|
1168
1233
|
|
|
1169
1234
|
function initKittyKeyboard(): void {
|
|
@@ -1215,13 +1280,7 @@ export const createInk = (options: Options): Ink => {
|
|
|
1215
1280
|
}
|
|
1216
1281
|
|
|
1217
1282
|
function beginSuspend(): void {
|
|
1218
|
-
|
|
1219
|
-
throw new Error(
|
|
1220
|
-
"The terminal is already suspended. Resume the current suspension before suspending again.",
|
|
1221
|
-
);
|
|
1222
|
-
}
|
|
1223
|
-
|
|
1224
|
-
isSuspended = true;
|
|
1283
|
+
terminal.beginSuspension();
|
|
1225
1284
|
|
|
1226
1285
|
if (!interactive || isUnmounted || isUnmounting) {
|
|
1227
1286
|
return;
|
|
@@ -1230,9 +1289,8 @@ export const createInk = (options: Options): Ink => {
|
|
|
1230
1289
|
try {
|
|
1231
1290
|
const { canWriteToStdout } = getWritableStreamState(options.stdout);
|
|
1232
1291
|
|
|
1233
|
-
// Flush any pending render
|
|
1234
|
-
settleThrottle(
|
|
1235
|
-
settleThrottle(logThrottle, canWriteToStdout);
|
|
1292
|
+
// Flush any pending render so the child starts from a settled screen.
|
|
1293
|
+
settleThrottle(renderScheduler.throttled, canWriteToStdout);
|
|
1236
1294
|
|
|
1237
1295
|
if (canWriteToStdout) {
|
|
1238
1296
|
flushCapturedStdio();
|
|
@@ -1241,14 +1299,14 @@ export const createInk = (options: Options): Ink => {
|
|
|
1241
1299
|
if (canWriteToStdout) {
|
|
1242
1300
|
// Erase Ink's current frame, then show the cursor and re-arm the hide.
|
|
1243
1301
|
// The forced redraw on resume hides the cursor again.
|
|
1244
|
-
|
|
1245
|
-
|
|
1302
|
+
clearLiveOutput();
|
|
1303
|
+
finishLiveOutput();
|
|
1246
1304
|
|
|
1247
1305
|
if (kittyProtocolEnabled) {
|
|
1248
1306
|
writeBestEffort(options.stdout, ansiEscapes.popKittyKeyboard);
|
|
1249
1307
|
}
|
|
1250
1308
|
|
|
1251
|
-
if (alternateScreen) {
|
|
1309
|
+
if (terminal.alternateScreen) {
|
|
1252
1310
|
writeBestEffort(options.stdout, ansiEscapes.exitAlternativeScreen);
|
|
1253
1311
|
}
|
|
1254
1312
|
}
|
|
@@ -1259,7 +1317,7 @@ export const createInk = (options: Options): Ink => {
|
|
|
1259
1317
|
// If handing over the terminal fails partway, don't strand the app in a
|
|
1260
1318
|
// suspended state with no way back. Best-effort reclaim input, clear the
|
|
1261
1319
|
// flag, and rethrow so the caller sees the failure.
|
|
1262
|
-
|
|
1320
|
+
terminal.resume();
|
|
1263
1321
|
|
|
1264
1322
|
try {
|
|
1265
1323
|
resumeInput?.();
|
|
@@ -1270,11 +1328,11 @@ export const createInk = (options: Options): Ink => {
|
|
|
1270
1328
|
}
|
|
1271
1329
|
|
|
1272
1330
|
async function endSuspend(): Promise<void> {
|
|
1273
|
-
if (!
|
|
1331
|
+
if (!terminal.suspended) {
|
|
1274
1332
|
return;
|
|
1275
1333
|
}
|
|
1276
1334
|
|
|
1277
|
-
|
|
1335
|
+
terminal.resume();
|
|
1278
1336
|
|
|
1279
1337
|
// Reclaim input even mid-unmount: pauseInput already ran in beginSuspend, so
|
|
1280
1338
|
// restoring it is symmetric regardless of any state change during suspension.
|
|
@@ -1287,7 +1345,7 @@ export const createInk = (options: Options): Ink => {
|
|
|
1287
1345
|
const { canWriteToStdout } = getWritableStreamState(options.stdout);
|
|
1288
1346
|
|
|
1289
1347
|
if (canWriteToStdout) {
|
|
1290
|
-
if (alternateScreen) {
|
|
1348
|
+
if (terminal.alternateScreen) {
|
|
1291
1349
|
writeBestEffort(options.stdout, ansiEscapes.enterAlternativeScreen);
|
|
1292
1350
|
}
|
|
1293
1351
|
|
|
@@ -1303,7 +1361,7 @@ export const createInk = (options: Options): Ink => {
|
|
|
1303
1361
|
lastOutput = "";
|
|
1304
1362
|
lastOutputToRender = "";
|
|
1305
1363
|
lastOutputHeight = 0;
|
|
1306
|
-
|
|
1364
|
+
resetLiveOutput();
|
|
1307
1365
|
|
|
1308
1366
|
try {
|
|
1309
1367
|
calculateLayout();
|
|
@@ -1318,5 +1376,7 @@ export const createInk = (options: Options): Ink => {
|
|
|
1318
1376
|
waitUntilExit,
|
|
1319
1377
|
waitUntilRenderFlush,
|
|
1320
1378
|
clear,
|
|
1379
|
+
copyToClipboard: (text, selection) => terminal.copyToClipboard(text, selection),
|
|
1380
|
+
setProgress: (state, value) => terminal.setProgress(state, value),
|
|
1321
1381
|
};
|
|
1322
1382
|
};
|