@alchemy.run/sigil 0.0.0-alpha.3 → 0.0.0-alpha.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/README.md +23 -13
- package/THIRD_PARTY_NOTICES.md +70 -23
- package/dist/Text-D5HUf3Fj.d.ts +452 -0
- package/dist/ansi.d.ts +137 -143
- package/dist/ansi.js +87 -2
- 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/{devtools-BhYGjb7h.js → devtools-DbthxoD1.js} +22 -23
- 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-Bmc2tRPk.d.ts +21 -0
- package/dist/index.d.ts +235 -754
- package/dist/index.js +1362 -3001
- 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-BOh__-65.js +324 -0
- package/dist/screen-BReKIheE.d.ts +40 -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-BJmzX2NJ.js +664 -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-C2sciZOo.js +1335 -0
- package/package.json +43 -17
- package/src/ansi/chalk.ts +12 -53
- package/src/ansi/cursor.ts +2 -4
- package/src/ansi/east-asian-width.ts +44 -0
- package/src/ansi/escapes.ts +18 -26
- package/src/ansi/graphemes.ts +8 -0
- package/src/ansi/hyperlink.ts +44 -0
- package/src/ansi/index.ts +14 -14
- package/src/ansi/osc.ts +77 -0
- package/src/ansi/sgr.ts +2 -5
- package/src/ansi/slice.ts +1 -1
- package/src/ansi/string-width.ts +91 -206
- package/src/ansi/strip.ts +13 -33
- package/src/ansi/tokenize.ts +96 -169
- package/src/ansi/truncate.ts +5 -3
- package/src/ansi/wrap.ts +13 -14
- package/src/ansi-tokenizer.ts +1 -1
- 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 +109 -22
- package/src/components/BackgroundContext.ts +2 -3
- package/src/components/Box.tsx +43 -60
- package/src/components/CursorContext.ts +1 -1
- package/src/components/ErrorBoundary.tsx +1 -1
- package/src/components/ErrorOverview.tsx +8 -7
- package/src/components/Hyperlink.tsx +56 -0
- package/src/components/Spacer.tsx +1 -1
- package/src/components/Static.tsx +1 -1
- package/src/components/StderrContext.ts +0 -2
- package/src/components/StdinContext.ts +0 -1
- package/src/components/StdoutContext.ts +1 -3
- package/src/components/TerminalOscContext.ts +25 -0
- package/src/components/Text.tsx +23 -46
- package/src/components/Transform.tsx +2 -2
- package/src/cursor-position.ts +1 -1
- package/src/devtools.ts +84 -24
- package/src/dom.ts +11 -8
- package/src/env.ts +12 -0
- package/src/get-max-width.ts +1 -1
- package/src/global.d.ts +6 -4
- package/src/{boxes.ts → glyphs.ts} +17 -18
- package/src/hooks/use-animation.ts +1 -1
- package/src/hooks/use-app.ts +1 -1
- package/src/hooks/use-box-metrics.ts +1 -1
- package/src/hooks/use-capabilities.ts +73 -0
- package/src/hooks/use-cursor.ts +3 -3
- package/src/hooks/use-focus-manager.ts +1 -1
- package/src/hooks/use-focus.ts +5 -6
- package/src/hooks/use-input.ts +4 -4
- package/src/hooks/use-is-screen-reader-enabled.ts +1 -1
- package/src/hooks/use-paste.ts +2 -2
- package/src/hooks/use-stderr.ts +1 -1
- package/src/hooks/use-stdin.ts +1 -1
- package/src/hooks/use-stdout.ts +1 -1
- package/src/hooks/use-terminal-osc.ts +59 -0
- package/src/hooks/use-window-size.ts +2 -2
- package/src/index.ts +88 -44
- package/src/ink.tsx +620 -745
- package/src/input-parser.ts +1 -1
- package/src/instances.ts +1 -1
- package/src/kitty-keyboard.ts +128 -0
- package/src/measure-element.ts +1 -1
- package/src/measure-text.ts +2 -2
- package/src/{render-node-to-output.ts → paint-tree.ts} +78 -53
- package/src/parse-keypress.ts +4 -5
- package/src/parse-stack-line.ts +0 -1
- package/src/patch-console.ts +44 -0
- package/src/reconciler.ts +36 -62
- package/src/render-background.ts +37 -16
- package/src/render-border.ts +97 -64
- package/src/render-frame.ts +83 -0
- package/src/render-to-string.ts +24 -9
- package/src/render.ts +24 -16
- 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/sanitize-ansi.ts +1 -1
- package/src/screen/ansi.ts +184 -0
- package/src/screen/canvas.ts +137 -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 +272 -0
- package/src/screen/serialize.ts +129 -0
- package/src/screen.ts +1 -0
- package/src/semantic-text-style.ts +118 -0
- package/src/signal-exit.ts +4 -5
- package/src/squash-text-nodes.ts +4 -7
- package/src/stream.ts +2 -4
- package/src/structured-text.ts +321 -0
- package/src/styles.ts +16 -17
- 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-size.ts +9 -8
- 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/types.ts +1 -6
- package/src/utils.ts +2 -2
- package/src/wrap-text.ts +4 -4
- package/src/yoga/config.ts +2 -2
- package/src/yoga/core/absoluteLayout.ts +15 -15
- package/src/yoga/core/baseline.ts +3 -3
- package/src/yoga/core/cache.ts +4 -4
- package/src/yoga/core/calculateLayout.ts +33 -27
- package/src/yoga/core/config.ts +1 -1
- package/src/yoga/core/flexLine.ts +3 -3
- package/src/yoga/core/helpers.ts +3 -3
- package/src/yoga/core/layoutResults.ts +4 -4
- package/src/yoga/core/node.ts +16 -16
- package/src/yoga/core/pixelGrid.ts +4 -4
- package/src/yoga/core/style.ts +11 -11
- package/src/yoga/core/types.ts +2 -2
- package/src/yoga/index.ts +6 -6
- package/src/yoga/node.ts +8 -8
- package/dist/sgr-CMfEpjSk.d.ts +0 -91
- package/dist/truncate-CBiyyZzw.js +0 -2156
- package/src/ansi/supports-color.ts +0 -213
- package/src/ansi/widest-line.ts +0 -12
- package/src/auto-bind.ts +0 -41
- package/src/colorize.ts +0 -60
- package/src/devtools-window-polyfill.ts +0 -73
- package/src/indent-string.ts +0 -16
- package/src/is-in-ci.ts +0 -7
- package/src/log-update.ts +0 -370
- package/src/output.ts +0 -308
- package/src/renderer.ts +0 -73
- package/src/write-synchronized.ts +0 -9
package/src/log-update.ts
DELETED
|
@@ -1,370 +0,0 @@
|
|
|
1
|
-
import { cliCursor } from "./ansi/cursor.ts";
|
|
2
|
-
import { ansiEscapes } from "./ansi/escapes.ts";
|
|
3
|
-
import {
|
|
4
|
-
type CursorPosition,
|
|
5
|
-
cursorPositionChanged,
|
|
6
|
-
buildCursorSuffix,
|
|
7
|
-
buildCursorOnlySequence,
|
|
8
|
-
buildReturnToBottomPrefix,
|
|
9
|
-
hideCursorEscape,
|
|
10
|
-
} from "./cursor-position.ts";
|
|
11
|
-
|
|
12
|
-
export type { CursorPosition } from "./cursor-position.ts";
|
|
13
|
-
|
|
14
|
-
export type LogUpdate = {
|
|
15
|
-
clear: () => void;
|
|
16
|
-
done: () => void;
|
|
17
|
-
reset: () => void;
|
|
18
|
-
sync: (str: string) => void;
|
|
19
|
-
setCursorPosition: (position: CursorPosition | undefined) => void;
|
|
20
|
-
isCursorDirty: () => boolean;
|
|
21
|
-
willRender: (str: string) => boolean;
|
|
22
|
-
(str: string): boolean;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
// Count visible lines in a string, ignoring the trailing empty element
|
|
26
|
-
// that `split('\n')` produces when the string ends with '\n'.
|
|
27
|
-
const visibleLineCount = (lines: string[], str: string): number =>
|
|
28
|
-
str.endsWith("\n") ? lines.length - 1 : lines.length;
|
|
29
|
-
|
|
30
|
-
const createStandard = (stream: NodeJS.WritableStream, { showCursor = false } = {}): LogUpdate => {
|
|
31
|
-
let previousLineCount = 0;
|
|
32
|
-
let previousOutput = "";
|
|
33
|
-
let hasHiddenCursor = false;
|
|
34
|
-
let cursorPosition: CursorPosition | undefined;
|
|
35
|
-
let cursorDirty = false;
|
|
36
|
-
let previousCursorPosition: CursorPosition | undefined;
|
|
37
|
-
let cursorWasShown = false;
|
|
38
|
-
|
|
39
|
-
const getActiveCursor = () => (cursorDirty ? cursorPosition : undefined);
|
|
40
|
-
const hasChanges = (str: string, activeCursor: CursorPosition | undefined): boolean => {
|
|
41
|
-
const cursorChanged = cursorPositionChanged(activeCursor, previousCursorPosition);
|
|
42
|
-
return str !== previousOutput || cursorChanged;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
const render = (str: string) => {
|
|
46
|
-
if (!showCursor && !hasHiddenCursor) {
|
|
47
|
-
cliCursor.hide(stream);
|
|
48
|
-
hasHiddenCursor = true;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Only use cursor if setCursorPosition was called since last render.
|
|
52
|
-
// This ensures stale positions don't persist after component unmount.
|
|
53
|
-
const activeCursor = getActiveCursor();
|
|
54
|
-
cursorDirty = false;
|
|
55
|
-
const cursorChanged = cursorPositionChanged(activeCursor, previousCursorPosition);
|
|
56
|
-
|
|
57
|
-
if (!hasChanges(str, activeCursor)) {
|
|
58
|
-
return false;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const lines = str.split("\n");
|
|
62
|
-
const cursorSuffix = buildCursorSuffix(lines.length - 1, activeCursor);
|
|
63
|
-
|
|
64
|
-
if (str === previousOutput && cursorChanged) {
|
|
65
|
-
stream.write(
|
|
66
|
-
buildCursorOnlySequence({
|
|
67
|
-
cursorWasShown,
|
|
68
|
-
previousLineCount,
|
|
69
|
-
previousCursorPosition,
|
|
70
|
-
cursorPosition: activeCursor,
|
|
71
|
-
}),
|
|
72
|
-
);
|
|
73
|
-
} else {
|
|
74
|
-
previousOutput = str;
|
|
75
|
-
const returnPrefix = buildReturnToBottomPrefix(
|
|
76
|
-
cursorWasShown,
|
|
77
|
-
previousLineCount,
|
|
78
|
-
previousCursorPosition,
|
|
79
|
-
);
|
|
80
|
-
stream.write(returnPrefix + ansiEscapes.eraseLines(previousLineCount) + str + cursorSuffix);
|
|
81
|
-
previousLineCount = lines.length;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
previousCursorPosition = activeCursor ? { ...activeCursor } : undefined;
|
|
85
|
-
cursorWasShown = activeCursor !== undefined;
|
|
86
|
-
return true;
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
render.clear = () => {
|
|
90
|
-
const prefix = buildReturnToBottomPrefix(
|
|
91
|
-
cursorWasShown,
|
|
92
|
-
previousLineCount,
|
|
93
|
-
previousCursorPosition,
|
|
94
|
-
);
|
|
95
|
-
stream.write(prefix + ansiEscapes.eraseLines(previousLineCount));
|
|
96
|
-
previousOutput = "";
|
|
97
|
-
previousLineCount = 0;
|
|
98
|
-
previousCursorPosition = undefined;
|
|
99
|
-
cursorWasShown = false;
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
render.done = () => {
|
|
103
|
-
previousOutput = "";
|
|
104
|
-
previousLineCount = 0;
|
|
105
|
-
previousCursorPosition = undefined;
|
|
106
|
-
cursorWasShown = false;
|
|
107
|
-
|
|
108
|
-
if (!showCursor) {
|
|
109
|
-
cliCursor.show(stream);
|
|
110
|
-
hasHiddenCursor = false;
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
render.reset = () => {
|
|
115
|
-
previousOutput = "";
|
|
116
|
-
previousLineCount = 0;
|
|
117
|
-
previousCursorPosition = undefined;
|
|
118
|
-
cursorWasShown = false;
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
render.sync = (str: string) => {
|
|
122
|
-
const activeCursor = cursorDirty ? cursorPosition : undefined;
|
|
123
|
-
cursorDirty = false;
|
|
124
|
-
|
|
125
|
-
const lines = str.split("\n");
|
|
126
|
-
previousOutput = str;
|
|
127
|
-
previousLineCount = lines.length;
|
|
128
|
-
|
|
129
|
-
if (!activeCursor && cursorWasShown) {
|
|
130
|
-
stream.write(hideCursorEscape);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (activeCursor) {
|
|
134
|
-
stream.write(buildCursorSuffix(lines.length - 1, activeCursor));
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
previousCursorPosition = activeCursor ? { ...activeCursor } : undefined;
|
|
138
|
-
cursorWasShown = activeCursor !== undefined;
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
render.setCursorPosition = (position: CursorPosition | undefined) => {
|
|
142
|
-
cursorPosition = position;
|
|
143
|
-
cursorDirty = true;
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
render.isCursorDirty = () => cursorDirty;
|
|
147
|
-
render.willRender = (str: string) => hasChanges(str, getActiveCursor());
|
|
148
|
-
|
|
149
|
-
return render;
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
const createIncremental = (
|
|
153
|
-
stream: NodeJS.WritableStream,
|
|
154
|
-
{ showCursor = false } = {},
|
|
155
|
-
): LogUpdate => {
|
|
156
|
-
let previousLines: string[] = [];
|
|
157
|
-
let previousOutput = "";
|
|
158
|
-
let hasHiddenCursor = false;
|
|
159
|
-
let cursorPosition: CursorPosition | undefined;
|
|
160
|
-
let cursorDirty = false;
|
|
161
|
-
let previousCursorPosition: CursorPosition | undefined;
|
|
162
|
-
let cursorWasShown = false;
|
|
163
|
-
|
|
164
|
-
const getActiveCursor = () => (cursorDirty ? cursorPosition : undefined);
|
|
165
|
-
const hasChanges = (str: string, activeCursor: CursorPosition | undefined): boolean => {
|
|
166
|
-
const cursorChanged = cursorPositionChanged(activeCursor, previousCursorPosition);
|
|
167
|
-
return str !== previousOutput || cursorChanged;
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
const render = (str: string) => {
|
|
171
|
-
if (!showCursor && !hasHiddenCursor) {
|
|
172
|
-
cliCursor.hide(stream);
|
|
173
|
-
hasHiddenCursor = true;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Only use cursor if setCursorPosition was called since last render.
|
|
177
|
-
// This ensures stale positions don't persist after component unmount.
|
|
178
|
-
const activeCursor = getActiveCursor();
|
|
179
|
-
cursorDirty = false;
|
|
180
|
-
const cursorChanged = cursorPositionChanged(activeCursor, previousCursorPosition);
|
|
181
|
-
|
|
182
|
-
if (!hasChanges(str, activeCursor)) {
|
|
183
|
-
return false;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const nextLines = str.split("\n");
|
|
187
|
-
const visibleCount = visibleLineCount(nextLines, str);
|
|
188
|
-
const previousVisible = visibleLineCount(previousLines, previousOutput);
|
|
189
|
-
|
|
190
|
-
if (str === previousOutput && cursorChanged) {
|
|
191
|
-
stream.write(
|
|
192
|
-
buildCursorOnlySequence({
|
|
193
|
-
cursorWasShown,
|
|
194
|
-
previousLineCount: previousLines.length,
|
|
195
|
-
previousCursorPosition,
|
|
196
|
-
cursorPosition: activeCursor,
|
|
197
|
-
}),
|
|
198
|
-
);
|
|
199
|
-
previousCursorPosition = activeCursor ? { ...activeCursor } : undefined;
|
|
200
|
-
cursorWasShown = activeCursor !== undefined;
|
|
201
|
-
return true;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const returnPrefix = buildReturnToBottomPrefix(
|
|
205
|
-
cursorWasShown,
|
|
206
|
-
previousLines.length,
|
|
207
|
-
previousCursorPosition,
|
|
208
|
-
);
|
|
209
|
-
|
|
210
|
-
if (str === "\n" || previousOutput.length === 0) {
|
|
211
|
-
const cursorSuffix = buildCursorSuffix(nextLines.length - 1, activeCursor);
|
|
212
|
-
stream.write(
|
|
213
|
-
returnPrefix + ansiEscapes.eraseLines(previousLines.length) + str + cursorSuffix,
|
|
214
|
-
);
|
|
215
|
-
cursorWasShown = activeCursor !== undefined;
|
|
216
|
-
previousCursorPosition = activeCursor ? { ...activeCursor } : undefined;
|
|
217
|
-
previousOutput = str;
|
|
218
|
-
previousLines = nextLines;
|
|
219
|
-
return true;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// A frame that grew may need rows past the bottom margin. The
|
|
223
|
-
// incremental walk moves over unchanged lines with cursorNextLine,
|
|
224
|
-
// which clamps at the bottom margin instead of scrolling (only a
|
|
225
|
-
// line feed scrolls), so a grown frame that reaches the bottom
|
|
226
|
-
// desynchronizes the tracked cursor position from the terminal.
|
|
227
|
-
// Rewrite the whole frame instead — every line is emitted with a
|
|
228
|
-
// newline, which scrolls correctly at the bottom.
|
|
229
|
-
if (visibleCount > previousVisible) {
|
|
230
|
-
const cursorSuffix = buildCursorSuffix(visibleCount, activeCursor);
|
|
231
|
-
stream.write(
|
|
232
|
-
returnPrefix + ansiEscapes.eraseLines(previousLines.length) + str + cursorSuffix,
|
|
233
|
-
);
|
|
234
|
-
cursorWasShown = activeCursor !== undefined;
|
|
235
|
-
previousCursorPosition = activeCursor ? { ...activeCursor } : undefined;
|
|
236
|
-
previousOutput = str;
|
|
237
|
-
previousLines = nextLines;
|
|
238
|
-
return true;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
const hasTrailingNewline = str.endsWith("\n");
|
|
242
|
-
|
|
243
|
-
// We aggregate all chunks for incremental rendering into a buffer, and then write them to stdout at the end.
|
|
244
|
-
const buffer: string[] = [];
|
|
245
|
-
|
|
246
|
-
buffer.push(returnPrefix);
|
|
247
|
-
|
|
248
|
-
// Clear extra lines if the current content's line count is lower than the previous.
|
|
249
|
-
if (visibleCount < previousVisible) {
|
|
250
|
-
const previousHadTrailingNewline = previousOutput.endsWith("\n");
|
|
251
|
-
const extraSlot = previousHadTrailingNewline ? 1 : 0;
|
|
252
|
-
buffer.push(
|
|
253
|
-
ansiEscapes.eraseLines(previousVisible - visibleCount + extraSlot),
|
|
254
|
-
ansiEscapes.cursorUp(visibleCount),
|
|
255
|
-
);
|
|
256
|
-
} else {
|
|
257
|
-
buffer.push(ansiEscapes.cursorUp(previousLines.length - 1));
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
for (let i = 0; i < visibleCount; i++) {
|
|
261
|
-
const isLastLine = i === visibleCount - 1;
|
|
262
|
-
|
|
263
|
-
// We do not write lines if the contents are the same. This prevents flickering during renders.
|
|
264
|
-
if (nextLines[i] === previousLines[i]) {
|
|
265
|
-
// Don't move past the last line when there's no trailing newline,
|
|
266
|
-
// otherwise the cursor overshoots the rendered block.
|
|
267
|
-
if (!isLastLine || hasTrailingNewline) {
|
|
268
|
-
buffer.push(ansiEscapes.cursorNextLine);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
continue;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
buffer.push(
|
|
275
|
-
ansiEscapes.cursorTo(0) +
|
|
276
|
-
nextLines[i] +
|
|
277
|
-
ansiEscapes.eraseEndLine +
|
|
278
|
-
// Don't append newline after the last line when the input
|
|
279
|
-
// has no trailing newline (fullscreen mode).
|
|
280
|
-
(isLastLine && !hasTrailingNewline ? "" : "\n"),
|
|
281
|
-
);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
const cursorSuffix = buildCursorSuffix(nextLines.length - 1, activeCursor);
|
|
285
|
-
buffer.push(cursorSuffix);
|
|
286
|
-
|
|
287
|
-
stream.write(buffer.join(""));
|
|
288
|
-
|
|
289
|
-
cursorWasShown = activeCursor !== undefined;
|
|
290
|
-
previousCursorPosition = activeCursor ? { ...activeCursor } : undefined;
|
|
291
|
-
previousOutput = str;
|
|
292
|
-
previousLines = nextLines;
|
|
293
|
-
return true;
|
|
294
|
-
};
|
|
295
|
-
|
|
296
|
-
render.clear = () => {
|
|
297
|
-
const prefix = buildReturnToBottomPrefix(
|
|
298
|
-
cursorWasShown,
|
|
299
|
-
previousLines.length,
|
|
300
|
-
previousCursorPosition,
|
|
301
|
-
);
|
|
302
|
-
stream.write(prefix + ansiEscapes.eraseLines(previousLines.length));
|
|
303
|
-
previousOutput = "";
|
|
304
|
-
previousLines = [];
|
|
305
|
-
previousCursorPosition = undefined;
|
|
306
|
-
cursorWasShown = false;
|
|
307
|
-
};
|
|
308
|
-
|
|
309
|
-
render.done = () => {
|
|
310
|
-
previousOutput = "";
|
|
311
|
-
previousLines = [];
|
|
312
|
-
previousCursorPosition = undefined;
|
|
313
|
-
cursorWasShown = false;
|
|
314
|
-
|
|
315
|
-
if (!showCursor) {
|
|
316
|
-
cliCursor.show(stream);
|
|
317
|
-
hasHiddenCursor = false;
|
|
318
|
-
}
|
|
319
|
-
};
|
|
320
|
-
|
|
321
|
-
render.reset = () => {
|
|
322
|
-
previousOutput = "";
|
|
323
|
-
previousLines = [];
|
|
324
|
-
previousCursorPosition = undefined;
|
|
325
|
-
cursorWasShown = false;
|
|
326
|
-
};
|
|
327
|
-
|
|
328
|
-
render.sync = (str: string) => {
|
|
329
|
-
const activeCursor = cursorDirty ? cursorPosition : undefined;
|
|
330
|
-
cursorDirty = false;
|
|
331
|
-
|
|
332
|
-
const lines = str.split("\n");
|
|
333
|
-
previousOutput = str;
|
|
334
|
-
previousLines = lines;
|
|
335
|
-
|
|
336
|
-
if (!activeCursor && cursorWasShown) {
|
|
337
|
-
stream.write(hideCursorEscape);
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
if (activeCursor) {
|
|
341
|
-
stream.write(buildCursorSuffix(lines.length - 1, activeCursor));
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
previousCursorPosition = activeCursor ? { ...activeCursor } : undefined;
|
|
345
|
-
cursorWasShown = activeCursor !== undefined;
|
|
346
|
-
};
|
|
347
|
-
|
|
348
|
-
render.setCursorPosition = (position: CursorPosition | undefined) => {
|
|
349
|
-
cursorPosition = position;
|
|
350
|
-
cursorDirty = true;
|
|
351
|
-
};
|
|
352
|
-
|
|
353
|
-
render.isCursorDirty = () => cursorDirty;
|
|
354
|
-
render.willRender = (str: string) => hasChanges(str, getActiveCursor());
|
|
355
|
-
|
|
356
|
-
return render;
|
|
357
|
-
};
|
|
358
|
-
|
|
359
|
-
const create = (
|
|
360
|
-
stream: NodeJS.WritableStream,
|
|
361
|
-
{ showCursor = false, incremental = false } = {},
|
|
362
|
-
): LogUpdate => {
|
|
363
|
-
if (incremental) {
|
|
364
|
-
return createIncremental(stream, { showCursor });
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
return createStandard(stream, { showCursor });
|
|
368
|
-
};
|
|
369
|
-
|
|
370
|
-
export const logUpdate = { create };
|
package/src/output.ts
DELETED
|
@@ -1,308 +0,0 @@
|
|
|
1
|
-
import { sliceAnsi } from "./ansi/slice.ts";
|
|
2
|
-
import { stringWidth } from "./ansi/string-width.ts";
|
|
3
|
-
import {
|
|
4
|
-
type StyledChar,
|
|
5
|
-
styledCharsFromTokens,
|
|
6
|
-
styledCharsToString,
|
|
7
|
-
tokenize,
|
|
8
|
-
} from "./ansi/tokenize.ts";
|
|
9
|
-
import { type OutputTransformer } from "./render-node-to-output.ts";
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
"Virtual" output class
|
|
13
|
-
|
|
14
|
-
Handles the positioning and saving of the output of each node in the tree. Also responsible for applying transformations to each character of the output.
|
|
15
|
-
|
|
16
|
-
Used to generate the final output of all nodes before writing it to actual output stream (e.g. stdout)
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
type Options = {
|
|
20
|
-
width: number;
|
|
21
|
-
height: number;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
type Operation = WriteOperation | ClipOperation | UnclipOperation;
|
|
25
|
-
|
|
26
|
-
type WriteOperation = {
|
|
27
|
-
type: "write";
|
|
28
|
-
x: number;
|
|
29
|
-
y: number;
|
|
30
|
-
text: string;
|
|
31
|
-
transformers: OutputTransformer[];
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
type ClipOperation = {
|
|
35
|
-
type: "clip";
|
|
36
|
-
clip: Clip;
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
type Clip = {
|
|
40
|
-
x1: number | undefined;
|
|
41
|
-
x2: number | undefined;
|
|
42
|
-
y1: number | undefined;
|
|
43
|
-
y2: number | undefined;
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
type UnclipOperation = {
|
|
47
|
-
type: "unclip";
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
class OutputCaches {
|
|
51
|
-
widths = new Map<string, number>();
|
|
52
|
-
blockWidths = new Map<string, number>();
|
|
53
|
-
styledChars = new Map<string, StyledChar[]>();
|
|
54
|
-
|
|
55
|
-
getStyledChars(line: string): StyledChar[] {
|
|
56
|
-
let cached = this.styledChars.get(line);
|
|
57
|
-
if (cached === undefined) {
|
|
58
|
-
cached = styledCharsFromTokens(tokenize(line));
|
|
59
|
-
this.styledChars.set(line, cached);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return cached;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
getStringWidth(text: string): number {
|
|
66
|
-
let cached = this.widths.get(text);
|
|
67
|
-
if (cached === undefined) {
|
|
68
|
-
cached = stringWidth(text);
|
|
69
|
-
this.widths.set(text, cached);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return cached;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
getWidestLine(text: string): number {
|
|
76
|
-
let cached = this.blockWidths.get(text);
|
|
77
|
-
if (cached === undefined) {
|
|
78
|
-
let lineWidth = 0;
|
|
79
|
-
for (const line of text.split("\n")) {
|
|
80
|
-
lineWidth = Math.max(lineWidth, this.getStringWidth(line));
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
cached = lineWidth;
|
|
84
|
-
this.blockWidths.set(text, cached);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
return cached;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export class Output {
|
|
92
|
-
width: number;
|
|
93
|
-
height: number;
|
|
94
|
-
|
|
95
|
-
private readonly operations: Operation[] = [];
|
|
96
|
-
private readonly caches: OutputCaches = new OutputCaches();
|
|
97
|
-
|
|
98
|
-
constructor(options: Options) {
|
|
99
|
-
const { width, height } = options;
|
|
100
|
-
|
|
101
|
-
this.width = width;
|
|
102
|
-
this.height = height;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
write(x: number, y: number, text: string, options: { transformers: OutputTransformer[] }): void {
|
|
106
|
-
const { transformers } = options;
|
|
107
|
-
|
|
108
|
-
if (!text) {
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
this.operations.push({
|
|
113
|
-
type: "write",
|
|
114
|
-
x,
|
|
115
|
-
y,
|
|
116
|
-
text,
|
|
117
|
-
transformers,
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
clip(clip: Clip) {
|
|
122
|
-
this.operations.push({
|
|
123
|
-
type: "clip",
|
|
124
|
-
clip,
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
unclip() {
|
|
129
|
-
this.operations.push({
|
|
130
|
-
type: "unclip",
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
get(): { output: string; height: number } {
|
|
135
|
-
// Initialize output array with a specific set of rows, so that margin/padding at the bottom is preserved
|
|
136
|
-
const output: StyledChar[][] = [];
|
|
137
|
-
|
|
138
|
-
for (let y = 0; y < this.height; y++) {
|
|
139
|
-
const row: StyledChar[] = [];
|
|
140
|
-
|
|
141
|
-
for (let x = 0; x < this.width; x++) {
|
|
142
|
-
row.push({
|
|
143
|
-
type: "char",
|
|
144
|
-
value: " ",
|
|
145
|
-
fullWidth: false,
|
|
146
|
-
styles: [],
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
output.push(row);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const clips: Clip[] = [];
|
|
154
|
-
|
|
155
|
-
for (const operation of this.operations) {
|
|
156
|
-
if (operation.type === "clip") {
|
|
157
|
-
clips.push(operation.clip);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
if (operation.type === "unclip") {
|
|
161
|
-
clips.pop();
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if (operation.type === "write") {
|
|
165
|
-
const { text, transformers } = operation;
|
|
166
|
-
let { x, y } = operation;
|
|
167
|
-
let lines = text.split("\n");
|
|
168
|
-
|
|
169
|
-
const clip = clips.at(-1);
|
|
170
|
-
|
|
171
|
-
if (clip) {
|
|
172
|
-
const clipHorizontally = typeof clip?.x1 === "number" && typeof clip?.x2 === "number";
|
|
173
|
-
|
|
174
|
-
const clipVertically = typeof clip?.y1 === "number" && typeof clip?.y2 === "number";
|
|
175
|
-
|
|
176
|
-
// If text is positioned outside of clipping area altogether,
|
|
177
|
-
// skip to the next operation to avoid unnecessary calculations
|
|
178
|
-
if (clipHorizontally) {
|
|
179
|
-
const width = this.caches.getWidestLine(text);
|
|
180
|
-
|
|
181
|
-
if (x + width < clip.x1! || x > clip.x2!) {
|
|
182
|
-
continue;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if (clipVertically) {
|
|
187
|
-
const height = lines.length;
|
|
188
|
-
|
|
189
|
-
if (y + height < clip.y1! || y > clip.y2!) {
|
|
190
|
-
continue;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
if (clipHorizontally) {
|
|
195
|
-
lines = lines.map((line) => {
|
|
196
|
-
const from = x < clip.x1! ? clip.x1! - x : 0;
|
|
197
|
-
const width = this.caches.getStringWidth(line);
|
|
198
|
-
const to = x + width > clip.x2! ? clip.x2! - x : width;
|
|
199
|
-
|
|
200
|
-
return sliceAnsi(line, from, to);
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
if (x < clip.x1!) {
|
|
204
|
-
x = clip.x1!;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
if (clipVertically) {
|
|
209
|
-
const from = y < clip.y1! ? clip.y1! - y : 0;
|
|
210
|
-
const height = lines.length;
|
|
211
|
-
const to = y + height > clip.y2! ? clip.y2! - y : height;
|
|
212
|
-
|
|
213
|
-
lines = lines.slice(from, to);
|
|
214
|
-
|
|
215
|
-
if (y < clip.y1!) {
|
|
216
|
-
y = clip.y1!;
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
let offsetY = 0;
|
|
222
|
-
|
|
223
|
-
for (let [index, line] of lines.entries()) {
|
|
224
|
-
const currentLine = output[y + offsetY];
|
|
225
|
-
|
|
226
|
-
// Line can be missing if `text` is taller than height of pre-initialized `this.output`
|
|
227
|
-
if (!currentLine) {
|
|
228
|
-
continue;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
for (const transformer of transformers) {
|
|
232
|
-
line = transformer(line, index);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
const characters = this.caches.getStyledChars(line);
|
|
236
|
-
let offsetX = x;
|
|
237
|
-
|
|
238
|
-
// Nothing to write (e.g. line was clipped away).
|
|
239
|
-
if (characters.length === 0) {
|
|
240
|
-
offsetY++;
|
|
241
|
-
continue;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
const spaceCell: StyledChar = {
|
|
245
|
-
type: "char",
|
|
246
|
-
value: " ",
|
|
247
|
-
fullWidth: false,
|
|
248
|
-
styles: [],
|
|
249
|
-
};
|
|
250
|
-
|
|
251
|
-
// Wide characters (e.g. CJK) occupy two cells: a leading
|
|
252
|
-
// cell with the character and a trailing placeholder with
|
|
253
|
-
// value ''. When an overlapping write lands in the middle
|
|
254
|
-
// of a wide character, the boundary cells need cleanup so
|
|
255
|
-
// the terminal never renders a half-visible wide character.
|
|
256
|
-
if (
|
|
257
|
-
currentLine[offsetX]?.value === "" &&
|
|
258
|
-
offsetX > 0 &&
|
|
259
|
-
this.caches.getStringWidth(currentLine[offsetX - 1]?.value ?? "") > 1
|
|
260
|
-
) {
|
|
261
|
-
currentLine[offsetX - 1] = spaceCell;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
for (const character of characters) {
|
|
265
|
-
currentLine[offsetX] = character;
|
|
266
|
-
|
|
267
|
-
// Determine printed width using string-width to align with measurement
|
|
268
|
-
const characterWidth = Math.max(1, this.caches.getStringWidth(character.value));
|
|
269
|
-
|
|
270
|
-
// For multi-column characters, clear following cells to avoid stray spaces/artifacts
|
|
271
|
-
if (characterWidth > 1) {
|
|
272
|
-
for (let columnOffset = 1; columnOffset < characterWidth; columnOffset++) {
|
|
273
|
-
currentLine[offsetX + columnOffset] = {
|
|
274
|
-
type: "char",
|
|
275
|
-
value: "",
|
|
276
|
-
fullWidth: false,
|
|
277
|
-
styles: character.styles,
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
offsetX += characterWidth;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
if (currentLine[offsetX]?.value === "") {
|
|
286
|
-
currentLine[offsetX] = spaceCell;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
offsetY++;
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
const generatedOutput = output
|
|
295
|
-
.map((line) => {
|
|
296
|
-
// See https://github.com/vadimdemedes/ink/pull/564#issuecomment-1637022742
|
|
297
|
-
const lineWithoutEmptyItems = line.filter((item) => item !== undefined);
|
|
298
|
-
|
|
299
|
-
return styledCharsToString(lineWithoutEmptyItems).trimEnd();
|
|
300
|
-
})
|
|
301
|
-
.join("\n");
|
|
302
|
-
|
|
303
|
-
return {
|
|
304
|
-
output: generatedOutput,
|
|
305
|
-
height: output.length,
|
|
306
|
-
};
|
|
307
|
-
}
|
|
308
|
-
}
|