@promptctl/rich-js 0.27.0 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -6
- package/THIRD-PARTY-NOTICES +42 -0
- package/dist/core/animation.d.ts +65 -46
- package/dist/core/animation.js +91 -67
- package/dist/core/ansi.d.ts +19 -12
- package/dist/core/ansi.js +114 -36
- package/dist/core/bidi-data.d.ts +14 -0
- package/dist/core/bidi-data.js +385 -0
- package/dist/core/bidi.d.ts +89 -0
- package/dist/core/bidi.js +615 -0
- package/dist/core/box.d.ts +9 -1
- package/dist/core/box.js +11 -0
- package/dist/core/cell-widths.d.ts +3 -2
- package/dist/core/cell-widths.js +93 -93
- package/dist/core/cells.d.ts +20 -0
- package/dist/core/cells.js +94 -6
- package/dist/core/clock.d.ts +1 -1
- package/dist/core/color.d.ts +56 -57
- package/dist/core/color.js +79 -81
- package/dist/core/console.d.ts +24 -7
- package/dist/core/console.js +27 -8
- package/dist/core/control-codes.d.ts +61 -0
- package/dist/core/control-codes.js +157 -0
- package/dist/core/easing.d.ts +40 -40
- package/dist/core/easing.js +40 -40
- package/dist/core/env.d.ts +6 -0
- package/dist/core/env.js +9 -1
- package/dist/core/export-html.js +5 -4
- package/dist/core/export-lines.d.ts +13 -11
- package/dist/core/export-lines.js +19 -15
- package/dist/core/export-svg.d.ts +3 -3
- package/dist/core/export-svg.js +10 -9
- package/dist/core/json.js +9 -2
- package/dist/core/markup.js +5 -4
- package/dist/core/memo.d.ts +1 -1
- package/dist/core/memo.js +1 -1
- package/dist/core/oklch.d.ts +14 -14
- package/dist/core/oklch.js +10 -10
- package/dist/core/orient.d.ts +68 -0
- package/dist/core/orient.js +224 -0
- package/dist/core/osc8.d.ts +12 -5
- package/dist/core/osc8.js +14 -7
- package/dist/core/paint.d.ts +9 -9
- package/dist/core/paint.js +3 -3
- package/dist/core/pretty.d.ts +1 -1
- package/dist/core/pretty.js +23 -19
- package/dist/core/protocol.d.ts +47 -26
- package/dist/core/protocol.js +31 -23
- package/dist/core/render.d.ts +19 -14
- package/dist/core/render.js +30 -17
- package/dist/core/seal.d.ts +26 -0
- package/dist/core/seal.js +75 -0
- package/dist/core/segment.d.ts +12 -4
- package/dist/core/segment.js +16 -6
- package/dist/core/strip.d.ts +83 -83
- package/dist/core/strip.js +70 -70
- package/dist/core/style.d.ts +46 -18
- package/dist/core/style.js +92 -56
- package/dist/core/text-order.d.ts +51 -0
- package/dist/core/text-order.js +50 -0
- package/dist/core/text.d.ts +12 -2
- package/dist/core/text.js +136 -90
- package/dist/core/timeline.d.ts +5 -5
- package/dist/core/timeline.js +11 -11
- package/dist/core/tween.d.ts +6 -6
- package/dist/core/tween.js +10 -10
- package/dist/core/view.d.ts +5 -5
- package/dist/core/view.js +15 -14
- package/dist/core/wrap.d.ts +24 -6
- package/dist/core/wrap.js +82 -16
- package/dist/host/app.d.ts +50 -9
- package/dist/host/app.js +120 -15
- package/dist/host/host-environment.d.ts +2 -2
- package/dist/host/host-environment.js +2 -2
- package/dist/host/index.d.ts +3 -2
- package/dist/host/index.js +1 -0
- package/dist/host/input.d.ts +42 -9
- package/dist/host/input.js +91 -7
- package/dist/host/terminal-colors.d.ts +18 -0
- package/dist/host/terminal-colors.js +46 -0
- package/dist/host/terminal-host.d.ts +4 -3
- package/dist/host/terminal-host.js +1 -1
- package/dist/index.d.ts +17 -16
- package/dist/index.js +11 -11
- package/dist/node/prompt.d.ts +1 -1
- package/dist/node/prompt.js +1 -1
- package/dist/node/terminal-host.d.ts +1 -1
- package/dist/node/traceback.js +2 -2
- package/dist/renderables/change.d.ts +1 -3
- package/dist/renderables/change.js +6 -8
- package/dist/renderables/effect.d.ts +88 -87
- package/dist/renderables/effect.js +124 -114
- package/dist/renderables/effects.d.ts +105 -76
- package/dist/renderables/effects.js +309 -176
- package/dist/renderables/embed.d.ts +1 -1
- package/dist/renderables/embed.js +2 -2
- package/dist/renderables/flexStrip.d.ts +7 -7
- package/dist/renderables/flexStrip.js +15 -15
- package/dist/renderables/frames.d.ts +7 -16
- package/dist/renderables/frames.js +12 -27
- package/dist/renderables/glyph-effect.d.ts +22 -23
- package/dist/renderables/glyph-effect.js +43 -41
- package/dist/renderables/glyph-effects.d.ts +33 -14
- package/dist/renderables/glyph-effects.js +90 -22
- package/dist/renderables/html-entities.d.ts +1 -1
- package/dist/renderables/html-entities.js +1 -1
- package/dist/renderables/lifecycle.d.ts +8 -10
- package/dist/renderables/lifecycle.js +17 -18
- package/dist/renderables/live.d.ts +2 -2
- package/dist/renderables/live.js +7 -7
- package/dist/renderables/markdown-parse.js +3 -3
- package/dist/renderables/markdown.js +4 -2
- package/dist/renderables/panel.js +7 -5
- package/dist/renderables/progress.d.ts +1 -1
- package/dist/renderables/progress.js +1 -1
- package/dist/renderables/progressBar.d.ts +3 -3
- package/dist/renderables/progressBar.js +21 -18
- package/dist/renderables/prompt.d.ts +1 -1
- package/dist/renderables/scroll.d.ts +1 -1
- package/dist/renderables/scroll.js +3 -3
- package/dist/renderables/spinner.d.ts +4 -4
- package/dist/renderables/spinner.js +10 -10
- package/dist/renderables/syntax.d.ts +2 -2
- package/dist/renderables/syntax.js +9 -4
- package/dist/renderables/table.js +5 -4
- package/dist/renderables/traceback.d.ts +1 -1
- package/dist/renderables/traceback.js +1 -1
- package/dist/renderables/tree.js +3 -3
- package/dist/template-bindings/animation-funcs.d.ts +60 -29
- package/dist/template-bindings/animation-funcs.js +126 -43
- package/dist/template-bindings/color-funcs.d.ts +14 -14
- package/dist/template-bindings/color-funcs.js +25 -25
- package/dist/template-bindings/index.d.ts +16 -14
- package/dist/template-bindings/index.js +17 -14
- package/dist/template-bindings/palette-funcs.d.ts +2 -2
- package/dist/template-bindings/palette-funcs.js +4 -4
- package/dist/template-bindings/style-funcs.d.ts +5 -5
- package/dist/template-bindings/style-funcs.js +16 -16
- package/dist/themes/{transpose.d.ts → adjust.d.ts} +23 -23
- package/dist/themes/{transpose.js → adjust.js} +28 -28
- package/dist/themes/buildPalette.d.ts +6 -5
- package/dist/themes/buildPalette.js +22 -22
- package/dist/themes/colorMath.d.ts +16 -16
- package/dist/themes/colorMath.js +32 -32
- package/dist/themes/data/ansi-tables.d.ts +2 -2
- package/dist/themes/data/ansi-tables.js +2 -2
- package/dist/themes/data/index.js +1 -4
- package/dist/themes/data/textual-ansi.js +0 -143
- package/dist/themes/data/types.d.ts +4 -4
- package/dist/themes/data/types.js +2 -2
- package/dist/themes/palette.d.ts +12 -12
- package/dist/themes/palette.js +16 -16
- package/dist/themes/ramp.d.ts +17 -17
- package/dist/themes/ramp.js +12 -12
- package/dist/themes/registry.d.ts +6 -8
- package/dist/themes/registry.js +6 -8
- package/dist/themes/terminalThemes.d.ts +4 -11
- package/dist/themes/terminalThemes.js +31 -47
- package/dist/widgets/button.d.ts +3 -3
- package/dist/widgets/button.js +8 -8
- package/dist/widgets/checkbox.d.ts +2 -2
- package/dist/widgets/checkbox.js +4 -4
- package/dist/widgets/dropdown.d.ts +3 -3
- package/dist/widgets/dropdown.js +7 -5
- package/dist/widgets/event-router.d.ts +6 -6
- package/dist/widgets/event-router.js +2 -2
- package/dist/widgets/focus-manager.d.ts +3 -3
- package/dist/widgets/focus-manager.js +3 -3
- package/dist/widgets/index.d.ts +2 -2
- package/dist/widgets/index.js +1 -1
- package/dist/widgets/ink.d.ts +3 -3
- package/dist/widgets/ink.js +4 -4
- package/dist/widgets/slider.d.ts +2 -2
- package/dist/widgets/static-item.d.ts +2 -2
- package/dist/widgets/text-input.d.ts +2 -2
- package/dist/widgets/text-input.js +5 -5
- package/dist/widgets/themed-widget.js +4 -4
- package/dist/widgets/toggle.d.ts +4 -4
- package/dist/widgets/toggle.js +6 -6
- package/dist/widgets/types.d.ts +6 -6
- package/dist/widgets/types.js +2 -2
- package/dist/widgets/widget-app.d.ts +10 -10
- package/dist/widgets/widget-app.js +8 -8
- package/dist/widgets/widget-base.d.ts +2 -2
- package/package.json +5 -4
package/dist/core/animation.js
CHANGED
|
@@ -2,23 +2,24 @@
|
|
|
2
2
|
* Animation — a value for a time, and when it next changes: the one type
|
|
3
3
|
* anything in rich-js that changes with time is an instance of.
|
|
4
4
|
*
|
|
5
|
-
* `at(t)` is the value at `t` seconds. `
|
|
5
|
+
* `at(t)` is the value at `t` seconds. `validUntil(t)` is the first moment,
|
|
6
6
|
* on `t`'s own timeline, at which the value may stop being `at(t)`: never
|
|
7
7
|
* before `t`. A later time says the value is held until then, `Infinity`
|
|
8
8
|
* that it never changes again, and `t` itself that it is moving at `t` — it
|
|
9
9
|
* changes every frame, however close together they are drawn. An early
|
|
10
10
|
* answer costs a host a frame that draws what the last one did; a late one
|
|
11
11
|
* skips a change. So an animation that cannot say answers `t`, as
|
|
12
|
-
* `continuous` does.
|
|
12
|
+
* `continuous` does. An answer that depends on what the frame drew, an
|
|
13
|
+
* effect's, is told by that drawing (`Reads`). A value drawn as cells carries this answer on every
|
|
13
14
|
* cell (`Style.hold`, both ends of the hold below), so a host holding
|
|
14
|
-
* the composed frame reads it there (`Segment.
|
|
15
|
-
* not drawn — a number, a
|
|
15
|
+
* the composed frame reads it there (`Segment.validUntil`); a value that is
|
|
16
|
+
* not drawn — a number, a color — has `validUntil` as its only way to say
|
|
16
17
|
* when to look again.
|
|
17
18
|
*
|
|
18
|
-
* `
|
|
19
|
+
* `validFrom(t)` is the other end of the same hold: the last moment, never
|
|
19
20
|
* after `t`, at which the value may have become `at(t)` — `-Infinity` if it
|
|
20
|
-
* always was, `t` while it moves. Together they bound the hold, `[
|
|
21
|
-
*
|
|
21
|
+
* always was, `t` while it moves. Together they bound the hold, `[validFrom(t),
|
|
22
|
+
* validUntil(t))`, and the answer may be narrower than the truth and never
|
|
22
23
|
* wider. It is what a host running the animation backwards reads
|
|
23
24
|
* (`./timeline.ts`): run down the timeline, the value next changes where it
|
|
24
25
|
* began. An animation that cannot say answers `t` here too.
|
|
@@ -37,10 +38,10 @@
|
|
|
37
38
|
* which content shows is a fact about time that motion is not: a value with
|
|
38
39
|
* no time could only show a scheduled exit as already gone.
|
|
39
40
|
*
|
|
40
|
-
* The instances already in the tree: a `
|
|
41
|
+
* The instances already in the tree: a `StepSequence` is an animation of which hold
|
|
41
42
|
* is in progress (`./easing.ts`), a `FrameSequence` of the frame shown and a
|
|
42
43
|
* `Spinner` of the spinner drawn (`../renderables/`), and an `Effect` is, for
|
|
43
|
-
* what an element draws in a frame, an animation of how its cells'
|
|
44
|
+
* what an element draws in a frame, an animation of how its cells' colors
|
|
44
45
|
* move (`../renderables/effect.ts`). Every kind of motion is one more.
|
|
45
46
|
*
|
|
46
47
|
* An animation of renderables is a renderable — its face, `placedRender`:
|
|
@@ -66,17 +67,17 @@
|
|
|
66
67
|
* of motion is built from — a constant, a value moving every moment, a map
|
|
67
68
|
* over the value, several at once, one animation after another, and one
|
|
68
69
|
* played again every period — and each says how
|
|
69
|
-
* `
|
|
70
|
+
* `validUntil` composes, so an animation built from them answers it without
|
|
70
71
|
* its author doing anything.
|
|
71
72
|
*
|
|
72
73
|
* Tier 5 of `src/core/`: `./easing.ts`, `./measure.ts`, `./protocol.ts`,
|
|
73
74
|
* `./segment.ts` and `./style.ts`, nothing else. [LAW:one-way-deps]
|
|
74
75
|
*/
|
|
75
|
-
import {
|
|
76
|
+
import { stepSequence, firstReaching, passAt, positiveSeconds } from "./easing.js";
|
|
76
77
|
import { Measurement } from "./measure.js";
|
|
77
78
|
import { isRenderable, render } from "./protocol.js";
|
|
78
79
|
import { Segment } from "./segment.js";
|
|
79
|
-
import { Style } from "./style.js";
|
|
80
|
+
import { intersection, NEVER_CHANGES, Style } from "./style.js";
|
|
80
81
|
/**
|
|
81
82
|
* What a frame drawn at `t` shows of `animation` under `motion`, and when
|
|
82
83
|
* that changes: the animation's, or under reduced motion its reduced form's
|
|
@@ -88,25 +89,25 @@ import { Style } from "./style.js";
|
|
|
88
89
|
export function shownAt(animation, t, motion) {
|
|
89
90
|
const reduced = motion.reducedMotion === true;
|
|
90
91
|
const drawn = reduced ? animation.reduced() : animation;
|
|
91
|
-
return { value: drawn.at(t),
|
|
92
|
+
return { value: drawn.at(t), drawn, reduced };
|
|
92
93
|
}
|
|
93
|
-
/** The hold `shown`, drawn at `t`, is in: its animation's two answers there. */
|
|
94
|
-
export function
|
|
95
|
-
return {
|
|
94
|
+
/** The hold `shown`, drawn at `t`, is in: its animation's two answers there, given what its drawing said. */
|
|
95
|
+
export function validityOf(shown, t, moves) {
|
|
96
|
+
return { validFrom: shown.drawn.validFrom(t, moves), validUntil: shown.drawn.validUntil(t, moves) };
|
|
96
97
|
}
|
|
97
98
|
/** The style saying a cell is in `hold`. */
|
|
98
|
-
export function
|
|
99
|
-
return new Style({
|
|
99
|
+
export function stampValidity(validity) {
|
|
100
|
+
return new Style({ validity });
|
|
100
101
|
}
|
|
101
102
|
/**
|
|
102
103
|
* `segments`, every one carrying `hold` under whatever it already said: the
|
|
103
|
-
* later `
|
|
104
|
+
* later `validFrom` and the sooner `validUntil` win (`Style.add`), so a
|
|
104
105
|
* frame's hold is the intersection of its cells', narrower than the truth
|
|
105
106
|
* and never wider. [LAW:single-enforcer] The one way a time is put on drawn
|
|
106
107
|
* cells, by whatever draws for a time it knows.
|
|
107
108
|
*/
|
|
108
|
-
export function
|
|
109
|
-
return Segment.applyStyle(segments,
|
|
109
|
+
export function stampValidityFrame(segments, validity) {
|
|
110
|
+
return Segment.applyStyle(segments, stampValidity(validity));
|
|
110
111
|
}
|
|
111
112
|
/**
|
|
112
113
|
* The moment a frame drawn under `options` is drawn for: the frame's time,
|
|
@@ -127,22 +128,33 @@ function drawable(value) {
|
|
|
127
128
|
}
|
|
128
129
|
/**
|
|
129
130
|
* An animation placed in the frame `options` draws: its value at the frame's
|
|
130
|
-
* time (`frameTimeOf`, `shownAt`), drawn by `draw
|
|
131
|
-
* with its hold there under whatever
|
|
132
|
-
* the frame (`RenderOptions.
|
|
133
|
-
* element not yet entered — may have cells
|
|
134
|
-
* taking a row to say it in.
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
131
|
+
* time (`frameTimeOf`, `shownAt`), drawn whole by `draw` under the options it
|
|
132
|
+
* is handed, every segment then stamped with its hold there under whatever
|
|
133
|
+
* its own cells say, and the hold told to the frame (`RenderOptions.onValidity`),
|
|
134
|
+
* because a frame of no cells — an element not yet entered — may have cells
|
|
135
|
+
* later, and must say when without taking a row to say it in. Drawn first,
|
|
136
|
+
* because the hold is asked with what the drawing said (`Reads`); each report
|
|
137
|
+
* goes on outward too, since a value drawn inside a placement in this value
|
|
138
|
+
* may be a part of an animation placed further out. [LAW:single-enforcer]
|
|
139
|
+
* The one placement: an animation of renderables (`placedRender`), an effect
|
|
140
|
+
* and a widget drawing one cell from an animation of its own (a caret's
|
|
141
|
+
* blink) place through it, so how a time reaches an animation and how its
|
|
142
|
+
* hold reaches the frame is written once.
|
|
139
143
|
*/
|
|
140
144
|
export function* placed(animation, options, draw) {
|
|
141
145
|
const t = frameTimeOf(options);
|
|
142
146
|
const shown = shownAt(animation, t, options);
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
147
|
+
const said = new Map();
|
|
148
|
+
const part = (at, key) => `${at} ${key}`;
|
|
149
|
+
const onReads = (at, key, validity) => {
|
|
150
|
+
const known = said.get(part(at, key));
|
|
151
|
+
said.set(part(at, key), known === undefined ? validity : intersection(known, validity));
|
|
152
|
+
options.onReads?.(at, key, validity);
|
|
153
|
+
};
|
|
154
|
+
const segments = [...draw(shown.value, { ...options, onReads })];
|
|
155
|
+
const validity = validityOf(shown, t, (at, key) => said.get(part(at, key)));
|
|
156
|
+
options.onValidity?.(validity);
|
|
157
|
+
yield* stampValidityFrame(segments, validity);
|
|
146
158
|
}
|
|
147
159
|
/**
|
|
148
160
|
* An animation's face, `render`: `placed`, its value drawn as the renderable
|
|
@@ -151,19 +163,26 @@ export function* placed(animation, options, draw) {
|
|
|
151
163
|
* the hold goes back the way the time came in. [LAW:composability]
|
|
152
164
|
*/
|
|
153
165
|
export function placedRender(animation, options) {
|
|
154
|
-
return placed(animation, options, (value) => render(drawable(value),
|
|
166
|
+
return placed(animation, options, (value, drawing) => render(drawable(value), drawing));
|
|
155
167
|
}
|
|
156
168
|
/**
|
|
157
169
|
* An animation's face, `measure`: what it shows at the frame's time, measured,
|
|
158
|
-
* and its hold there told (`RenderOptions.
|
|
170
|
+
* and its hold there told (`RenderOptions.onValidity`), because a container lays
|
|
159
171
|
* out from the measurement and must lay out again when it changes.
|
|
160
172
|
*/
|
|
161
173
|
export function placedMeasure(animation, options) {
|
|
162
174
|
const t = frameTimeOf(options);
|
|
163
175
|
const shown = shownAt(animation, t, options);
|
|
164
|
-
options.
|
|
176
|
+
options.onValidity?.(validityOf(shown, t, UNLAID));
|
|
165
177
|
return Measurement.get(options, drawable(shown.value));
|
|
166
178
|
}
|
|
179
|
+
/**
|
|
180
|
+
* What a measurement's drawing would say: held for good. An effect changes
|
|
181
|
+
* how cells look and never which exist, so nothing it does can change a
|
|
182
|
+
* layout; a combinator's own switches and a child's holds still reach the
|
|
183
|
+
* measurement. [LAW:types-are-the-program]
|
|
184
|
+
*/
|
|
185
|
+
const UNLAID = () => NEVER_CHANGES;
|
|
167
186
|
/**
|
|
168
187
|
* Every member of `source`, its own and its class's, as own properties of a
|
|
169
188
|
* new object: a method bound to `source`, a getter read off it live, a field
|
|
@@ -210,11 +229,11 @@ export const Animation = {
|
|
|
210
229
|
* is only ever a true never.
|
|
211
230
|
*/
|
|
212
231
|
done(animation, t) {
|
|
213
|
-
return animation.
|
|
232
|
+
return animation.validUntil(t) === Number.POSITIVE_INFINITY;
|
|
214
233
|
},
|
|
215
234
|
/** `value` at every time, never changing. */
|
|
216
235
|
constant(value) {
|
|
217
|
-
const still = Animation.of({ at: () => value,
|
|
236
|
+
const still = Animation.of({ at: () => value, validUntil: () => Number.POSITIVE_INFINITY, validFrom: () => Number.NEGATIVE_INFINITY, reduced: () => still });
|
|
218
237
|
return still;
|
|
219
238
|
},
|
|
220
239
|
/**
|
|
@@ -224,8 +243,8 @@ export const Animation = {
|
|
|
224
243
|
cut(before, after) {
|
|
225
244
|
const cut = Animation.of({
|
|
226
245
|
at: (t) => (t < 0 ? before : after),
|
|
227
|
-
|
|
228
|
-
|
|
246
|
+
validUntil: (t) => (t < 0 ? 0 : Number.POSITIVE_INFINITY),
|
|
247
|
+
validFrom: (t) => (t < 0 ? Number.NEGATIVE_INFINITY : 0),
|
|
229
248
|
reduced: () => cut,
|
|
230
249
|
});
|
|
231
250
|
return cut;
|
|
@@ -237,7 +256,7 @@ export const Animation = {
|
|
|
237
256
|
* it says its own end (`transition`, `../renderables/effects.ts`).
|
|
238
257
|
*/
|
|
239
258
|
continuous(at) {
|
|
240
|
-
return Animation.of({ at,
|
|
259
|
+
return Animation.of({ at, validUntil: (t) => t, validFrom: (t) => t, reduced: () => Animation.constant(at(0)) });
|
|
241
260
|
},
|
|
242
261
|
/**
|
|
243
262
|
* The time itself, in seconds, moving at every moment: what a clock, a
|
|
@@ -248,7 +267,7 @@ export const Animation = {
|
|
|
248
267
|
* [LAW:types-are-the-program]
|
|
249
268
|
*/
|
|
250
269
|
time() {
|
|
251
|
-
const time = Animation.of({ at: (t) => t,
|
|
270
|
+
const time = Animation.of({ at: (t) => t, validUntil: (t) => t, validFrom: (t) => t, reduced: () => time });
|
|
252
271
|
return time;
|
|
253
272
|
},
|
|
254
273
|
/**
|
|
@@ -258,8 +277,8 @@ export const Animation = {
|
|
|
258
277
|
map(animation, f) {
|
|
259
278
|
return Animation.of({
|
|
260
279
|
at: (t) => f(animation.at(t)),
|
|
261
|
-
|
|
262
|
-
|
|
280
|
+
validUntil: (t, moves) => animation.validUntil(t, moves),
|
|
281
|
+
validFrom: (t, moves) => animation.validFrom(t, moves),
|
|
263
282
|
reduced: () => Animation.map(animation.reduced(), f),
|
|
264
283
|
});
|
|
265
284
|
},
|
|
@@ -276,8 +295,8 @@ export const Animation = {
|
|
|
276
295
|
at: (t) => animations.map((animation) => animation.at(t)),
|
|
277
296
|
// A fold, not a spread: a stagger over a table's rows is a list as long
|
|
278
297
|
// as the table, past what an engine takes as arguments.
|
|
279
|
-
|
|
280
|
-
|
|
298
|
+
validUntil: (t, moves) => animations.reduce((soonest, animation) => Math.min(soonest, animation.validUntil(t, moves)), Number.POSITIVE_INFINITY),
|
|
299
|
+
validFrom: (t, moves) => animations.reduce((latest, animation) => Math.max(latest, animation.validFrom(t, moves)), Number.NEGATIVE_INFINITY),
|
|
281
300
|
reduced: () => Animation.all(animations.map((animation) => animation.reduced())),
|
|
282
301
|
});
|
|
283
302
|
},
|
|
@@ -293,21 +312,26 @@ export const Animation = {
|
|
|
293
312
|
* [LAW:one-source-of-truth] The one stepping rule, for an effect's frames
|
|
294
313
|
* and a transition's schedule alike.
|
|
295
314
|
*/
|
|
296
|
-
|
|
315
|
+
sampleAndHold(animation, stepSequence) {
|
|
297
316
|
return Animation.of({
|
|
298
|
-
at: (t) => animation.at(
|
|
299
|
-
|
|
300
|
-
const moved = animation.
|
|
317
|
+
at: (t) => animation.at(stepSequence.validFrom(t)),
|
|
318
|
+
validUntil(t, moves) {
|
|
319
|
+
const moved = animation.validUntil(stepSequence.validFrom(t), moves);
|
|
301
320
|
if (moved === Number.POSITIVE_INFINITY)
|
|
302
321
|
return moved;
|
|
303
|
-
const end =
|
|
322
|
+
const end = stepSequence.validUntil(t);
|
|
304
323
|
// The hold boundaries after `t` are `end`, then each hold's end in turn;
|
|
305
324
|
// the value turns at the first one the animation has moved by.
|
|
306
|
-
return moved <= end ? end :
|
|
325
|
+
return moved <= end ? end : stepSequence.validFrom(moved) === moved ? moved : stepSequence.validUntil(moved);
|
|
307
326
|
},
|
|
308
327
|
// The value held now began at this hold's start at the latest: an early
|
|
309
|
-
// answer where the animation had not moved by it, which the contract
|
|
310
|
-
|
|
328
|
+
// answer where the animation had not moved by it, which the contract
|
|
329
|
+
// allows. One that always was never turned on any beat.
|
|
330
|
+
validFrom(t, moves) {
|
|
331
|
+
const began = stepSequence.validFrom(t);
|
|
332
|
+
const moved = animation.validFrom(began, moves);
|
|
333
|
+
return moved === Number.NEGATIVE_INFINITY ? moved : began;
|
|
334
|
+
},
|
|
311
335
|
// Reduced, nothing moves between beats, so there is nothing to sample on them.
|
|
312
336
|
reduced: () => animation.reduced(),
|
|
313
337
|
});
|
|
@@ -331,19 +355,19 @@ export const Animation = {
|
|
|
331
355
|
const carried = (local) => (Number.isFinite(local) ? firstReaching(local + seconds, (x) => x - seconds >= local) : local);
|
|
332
356
|
return Animation.of({
|
|
333
357
|
at: (t) => (t - seconds >= 0 ? then.at(t - seconds) : first.at(t)),
|
|
334
|
-
|
|
358
|
+
validUntil(t, moves) {
|
|
335
359
|
const into = t - seconds;
|
|
336
360
|
if (into < 0)
|
|
337
|
-
return Math.min(first.
|
|
338
|
-
return Math.max(t, carried(then.
|
|
361
|
+
return Math.min(first.validUntil(t, moves), seconds);
|
|
362
|
+
return Math.max(t, carried(then.validUntil(into, moves)));
|
|
339
363
|
},
|
|
340
364
|
// After the switch the value became what it is at the switch or when
|
|
341
365
|
// `then`'s did since, whichever is later; before it, when `first`'s did.
|
|
342
|
-
|
|
366
|
+
validFrom(t, moves) {
|
|
343
367
|
const into = t - seconds;
|
|
344
368
|
if (into < 0)
|
|
345
|
-
return first.
|
|
346
|
-
return Math.min(t, Math.max(seconds, carried(then.
|
|
369
|
+
return first.validFrom(t, moves);
|
|
370
|
+
return Math.min(t, Math.max(seconds, carried(then.validFrom(into, moves))));
|
|
347
371
|
},
|
|
348
372
|
reduced: () => Animation.sequence(first.reduced(), seconds, then.reduced()),
|
|
349
373
|
});
|
|
@@ -363,10 +387,10 @@ export const Animation = {
|
|
|
363
387
|
repeat(period, nth) {
|
|
364
388
|
positiveSeconds("Animation.repeat period", period);
|
|
365
389
|
// [LAW:single-enforcer] Which turn a time is in, and how far into it, is
|
|
366
|
-
// `passAt`, the wrap a `beat` and `
|
|
390
|
+
// `passAt`, the wrap a `beat` and `WrapMode.loop` read time through, so a
|
|
367
391
|
// repeat beside a loop of the same period turns on the same float; where
|
|
368
392
|
// a turn begins and the next begins are a one-hold beat's two ends.
|
|
369
|
-
const turns =
|
|
393
|
+
const turns = stepSequence([period]);
|
|
370
394
|
// The turn's moment `local`, on its own clock, carried onto this one: the
|
|
371
395
|
// first float the wrap places at or past `local` in turn `n`, or in a
|
|
372
396
|
// later turn, found as `sequence` carries its second part's.
|
|
@@ -381,14 +405,14 @@ export const Animation = {
|
|
|
381
405
|
const { pass, into } = passAt("Animation.repeat", t, period);
|
|
382
406
|
return nth(pass).at(into);
|
|
383
407
|
},
|
|
384
|
-
|
|
408
|
+
validUntil(t, moves) {
|
|
385
409
|
const { pass, into } = passAt("Animation.repeat", t, period);
|
|
386
|
-
return Math.max(t, Math.min(turns.
|
|
410
|
+
return Math.max(t, Math.min(turns.validUntil(t), carried(pass, turns.validFrom(t), nth(pass).validUntil(into, moves))));
|
|
387
411
|
},
|
|
388
|
-
|
|
412
|
+
validFrom(t, moves) {
|
|
389
413
|
const { pass, into } = passAt("Animation.repeat", t, period);
|
|
390
|
-
const began = turns.
|
|
391
|
-
return Math.min(t, Math.max(began, carried(pass, began, nth(pass).
|
|
414
|
+
const began = turns.validFrom(t);
|
|
415
|
+
return Math.min(t, Math.max(began, carried(pass, began, nth(pass).validFrom(into, moves))));
|
|
392
416
|
},
|
|
393
417
|
reduced: () => Animation.repeat(period, (n) => nth(n).reduced()),
|
|
394
418
|
});
|
package/dist/core/ansi.d.ts
CHANGED
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
* own `new Console()`, a CLI's output shown inside a Panel, a log replayed into
|
|
8
8
|
* a Table cell.
|
|
9
9
|
*
|
|
10
|
-
* [LAW:types-are-the-program] Every
|
|
11
|
-
* escape names — `31` the standard
|
|
10
|
+
* [LAW:types-are-the-program] Every color decodes to the `ColorSpec` its
|
|
11
|
+
* escape names — `31` the standard color 1, `38;5;n` palette index n,
|
|
12
12
|
* `38;2;r;g;b` that truecolor — and none is resolved to RGB here. A standard
|
|
13
|
-
*
|
|
13
|
+
* color is a slot in whatever theme draws it, so one captured run exports
|
|
14
14
|
* correctly under a light theme and a dark one; resolved here, dark mode would
|
|
15
|
-
* show light mode's
|
|
15
|
+
* show light mode's colors. Resolution belongs to `export-lines`.
|
|
16
16
|
*
|
|
17
17
|
* [LAW:types-are-the-program] The link is decoder state of its own, beside
|
|
18
18
|
* the SGR style, not a field of it: a terminal ends a hyperlink only at an OSC
|
|
@@ -21,18 +21,25 @@
|
|
|
21
21
|
*
|
|
22
22
|
* Carriage returns are the other departure, and the same argument. Rich splits
|
|
23
23
|
* lines with Python's `splitlines`, which also breaks at `\r`, so its `a\rb`
|
|
24
|
-
* is two lines. Here a line ends
|
|
25
|
-
* column, so later text overwrites earlier text
|
|
26
|
-
* `50%\r100%` is `100%`, `Downloading\rDone` is `Doneloading`, and `done\r`
|
|
27
|
-
* is still `done`. Columns are
|
|
28
|
-
*
|
|
29
|
-
* Erase in line (`\x1b[K`) is honoured for the same reason, because a redraw
|
|
24
|
+
* is two lines. Here a line ends at `\n` and the census's other line breaks, and
|
|
25
|
+
* `\r` returns to the first column, so later text overwrites earlier text cell by
|
|
26
|
+
* cell: `50%\r100%` is `100%`, `Downloading\rDone` is `Doneloading`, and `done\r`
|
|
27
|
+
* is still `done`. Columns are a terminal's cells: a wide character takes two, a
|
|
28
|
+
* combining mark none, a tab moves to the next stop over what is there, and a wide
|
|
29
|
+
* character or a tab half overwritten leaves blanks. Erase in line (`\x1b[K`) is honoured for the same reason, because a redraw
|
|
30
30
|
* is usually `\r\x1b[K` and the old text must not show through a shorter new
|
|
31
31
|
* one.
|
|
32
32
|
*
|
|
33
|
+
* Backspace is the third. Rich drops it; here it is nroff's overstrike, the
|
|
34
|
+
* adjacent `x BS x` bold and `_ BS x` underline that `man` writes, and a bare one
|
|
35
|
+
* moves back a column, so `12%\b\b\b13%` is the redraw it looks like. The bytes
|
|
36
|
+
* alone cannot tell `1\b1` the redraw from `1\b1` the bold, and `less` reads them
|
|
37
|
+
* as overstrike with no other evidence, so this does.
|
|
38
|
+
*
|
|
33
39
|
* Everything else follows Rich: malformed SGR parameters are skipped rather
|
|
34
|
-
* than refused, and every other escape is dropped
|
|
35
|
-
* emulator — any other cursor movement is
|
|
40
|
+
* than refused, and every other escape is dropped, with every control a terminal
|
|
41
|
+
* draws nothing for. It is not a terminal emulator — any other cursor movement is
|
|
42
|
+
* dropped, not performed.
|
|
36
43
|
*
|
|
37
44
|
* Tier 5 of `src/core/`: it builds `RichText`, so it sits above `text`, and
|
|
38
45
|
* `RichText` cannot offer a `fromAnsi` static without importing upward.
|
package/dist/core/ansi.js
CHANGED
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
* own `new Console()`, a CLI's output shown inside a Panel, a log replayed into
|
|
8
8
|
* a Table cell.
|
|
9
9
|
*
|
|
10
|
-
* [LAW:types-are-the-program] Every
|
|
11
|
-
* escape names — `31` the standard
|
|
10
|
+
* [LAW:types-are-the-program] Every color decodes to the `ColorSpec` its
|
|
11
|
+
* escape names — `31` the standard color 1, `38;5;n` palette index n,
|
|
12
12
|
* `38;2;r;g;b` that truecolor — and none is resolved to RGB here. A standard
|
|
13
|
-
*
|
|
13
|
+
* color is a slot in whatever theme draws it, so one captured run exports
|
|
14
14
|
* correctly under a light theme and a dark one; resolved here, dark mode would
|
|
15
|
-
* show light mode's
|
|
15
|
+
* show light mode's colors. Resolution belongs to `export-lines`.
|
|
16
16
|
*
|
|
17
17
|
* [LAW:types-are-the-program] The link is decoder state of its own, beside
|
|
18
18
|
* the SGR style, not a field of it: a terminal ends a hyperlink only at an OSC
|
|
@@ -21,48 +21,78 @@
|
|
|
21
21
|
*
|
|
22
22
|
* Carriage returns are the other departure, and the same argument. Rich splits
|
|
23
23
|
* lines with Python's `splitlines`, which also breaks at `\r`, so its `a\rb`
|
|
24
|
-
* is two lines. Here a line ends
|
|
25
|
-
* column, so later text overwrites earlier text
|
|
26
|
-
* `50%\r100%` is `100%`, `Downloading\rDone` is `Doneloading`, and `done\r`
|
|
27
|
-
* is still `done`. Columns are
|
|
28
|
-
*
|
|
29
|
-
* Erase in line (`\x1b[K`) is honoured for the same reason, because a redraw
|
|
24
|
+
* is two lines. Here a line ends at `\n` and the census's other line breaks, and
|
|
25
|
+
* `\r` returns to the first column, so later text overwrites earlier text cell by
|
|
26
|
+
* cell: `50%\r100%` is `100%`, `Downloading\rDone` is `Doneloading`, and `done\r`
|
|
27
|
+
* is still `done`. Columns are a terminal's cells: a wide character takes two, a
|
|
28
|
+
* combining mark none, a tab moves to the next stop over what is there, and a wide
|
|
29
|
+
* character or a tab half overwritten leaves blanks. Erase in line (`\x1b[K`) is honoured for the same reason, because a redraw
|
|
30
30
|
* is usually `\r\x1b[K` and the old text must not show through a shorter new
|
|
31
31
|
* one.
|
|
32
32
|
*
|
|
33
|
+
* Backspace is the third. Rich drops it; here it is nroff's overstrike, the
|
|
34
|
+
* adjacent `x BS x` bold and `_ BS x` underline that `man` writes, and a bare one
|
|
35
|
+
* moves back a column, so `12%\b\b\b13%` is the redraw it looks like. The bytes
|
|
36
|
+
* alone cannot tell `1\b1` the redraw from `1\b1` the bold, and `less` reads them
|
|
37
|
+
* as overstrike with no other evidence, so this does.
|
|
38
|
+
*
|
|
33
39
|
* Everything else follows Rich: malformed SGR parameters are skipped rather
|
|
34
|
-
* than refused, and every other escape is dropped
|
|
35
|
-
* emulator — any other cursor movement is
|
|
40
|
+
* than refused, and every other escape is dropped, with every control a terminal
|
|
41
|
+
* draws nothing for. It is not a terminal emulator — any other cursor movement is
|
|
42
|
+
* dropped, not performed.
|
|
36
43
|
*
|
|
37
44
|
* Tier 5 of `src/core/`: it builds `RichText`, so it sits above `text`, and
|
|
38
45
|
* `RichText` cannot offer a `fromAnsi` static without importing upward.
|
|
39
46
|
*/
|
|
47
|
+
import { cellLen, graphemes } from "./cells.js";
|
|
40
48
|
import { ColorSpec } from "./color.js";
|
|
49
|
+
import { controlClass } from "./control-codes.js";
|
|
41
50
|
import { osc8Sequences } from "./osc8.js";
|
|
42
51
|
import { ATTRIBUTE_NAMES, ATTRIBUTE_SGR, NULL_STYLE, Style } from "./style.js";
|
|
43
52
|
import { RichText } from "./text.js";
|
|
53
|
+
const BOLD = new Style({ bold: true });
|
|
54
|
+
const UNDERLINED = new Style({ underline: true });
|
|
55
|
+
const BOLD_UNDERLINED = BOLD.add(UNDERLINED);
|
|
56
|
+
const STRING_ESCAPE = String.raw `\x1b[\]P_^X][^\n]*?(?:\x07|\x1b\\|\x9c|\n|$)`;
|
|
44
57
|
/**
|
|
45
58
|
* Every escape but OSC 8, which `osc8Sequences` reads before this runs. In
|
|
46
|
-
* order: a carriage return; an SGR sequence (
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
59
|
+
* order: a carriage return; an SGR sequence (`sgr`, its parameters — a private
|
|
60
|
+
* marker such as the `>` of `\x1b[>4;2m` makes it some other CSI); erase in line
|
|
61
|
+
* (`mode`); any other CSI sequence; any other string escape — OSC, DCS, APC, PM,
|
|
62
|
+
* SOS — run to the terminators OSC 8 accepts or cut off by the end of the line;
|
|
63
|
+
* any other escape, ECMA-48's intermediates and one final byte (`\x1b(B`,
|
|
64
|
+
* `\x1b)0`, `\x1b#8`, `\x1bc`); a character struck over itself (`struck`), over an
|
|
65
|
+
* underscore first or not (`underscore`); a character struck over an underscore
|
|
66
|
+
* alone (`underlined`); a backspace; and every control a line may not hold or a
|
|
67
|
+
* terminal draws nothing for. The silent ones are the two "any other" escapes and
|
|
68
|
+
* that last class.
|
|
53
69
|
*/
|
|
54
|
-
const
|
|
70
|
+
const ESCAPES = [
|
|
71
|
+
String.raw `\r|\x1b\[(?<sgr>[0-9;:]*)m|\x1b\[(?<mode>[012]?)K|\x1b\[[0-?]*[ -/]*[@-~]|${STRING_ESCAPE}|\x1b[ -/]*[0-~]`,
|
|
72
|
+
String.raw `(?<underscore>_\x08)?(?<struck>\P{Cc})(?:\x08\k<struck>)+|_\x08(?<underlined>\P{Cc})|\x08`,
|
|
73
|
+
controlClass((disposition) => disposition !== "keep" && disposition !== "zeroWidth"),
|
|
74
|
+
].join("|");
|
|
75
|
+
const escapes = () => new RegExp(ESCAPES, "gu");
|
|
76
|
+
// A string escape goes first so a line break in its payload stays in the payload.
|
|
77
|
+
const lineBreaks = () => new RegExp(`${STRING_ESCAPE}|${controlClass((disposition) => disposition === "line-break")}`, "gu");
|
|
55
78
|
function* escapeTokens(bytes) {
|
|
56
79
|
let at = 0;
|
|
57
80
|
for (const match of bytes.matchAll(escapes())) {
|
|
81
|
+
const { sgr, mode, underscore, struck, underlined } = match.groups;
|
|
58
82
|
if (match.index > at)
|
|
59
83
|
yield { kind: "text", text: bytes.slice(at, match.index) };
|
|
60
84
|
if (match[0] === "\r")
|
|
61
85
|
yield { kind: "return" };
|
|
62
|
-
if (
|
|
63
|
-
yield { kind: "sgr", params:
|
|
64
|
-
if (
|
|
65
|
-
yield { kind: "erase", mode: (
|
|
86
|
+
if (sgr !== undefined)
|
|
87
|
+
yield { kind: "sgr", params: sgr };
|
|
88
|
+
if (mode !== undefined)
|
|
89
|
+
yield { kind: "erase", mode: (mode || "0") };
|
|
90
|
+
if (struck !== undefined)
|
|
91
|
+
yield { kind: "overstrike", character: struck, emphasis: underscore === undefined ? BOLD : BOLD_UNDERLINED };
|
|
92
|
+
if (underlined !== undefined)
|
|
93
|
+
yield { kind: "overstrike", character: underlined, emphasis: UNDERLINED };
|
|
94
|
+
if (match[0] === "\b")
|
|
95
|
+
yield { kind: "backspace" };
|
|
66
96
|
at = match.index + match[0].length;
|
|
67
97
|
}
|
|
68
98
|
if (at < bytes.length)
|
|
@@ -90,7 +120,7 @@ function next(rest) {
|
|
|
90
120
|
}
|
|
91
121
|
/**
|
|
92
122
|
* `5;n` or `2;r;g;b`, the arguments of `38` and `48`. A sequence cut short
|
|
93
|
-
* names no
|
|
123
|
+
* names no color, and the codes it did consume are spent, as in Rich.
|
|
94
124
|
*/
|
|
95
125
|
function extendedColor(rest) {
|
|
96
126
|
const kind = next(rest);
|
|
@@ -115,8 +145,8 @@ const SGR_OPS = new Map([
|
|
|
115
145
|
[23, adding({ italic: false })],
|
|
116
146
|
// ECMA-48: 24 is "neither singly nor doubly underlined", 25 "steady" at
|
|
117
147
|
// either speed. Rich's table clears only the first of each pair.
|
|
118
|
-
[24, adding({ underline: false,
|
|
119
|
-
[25, adding({ blink: false,
|
|
148
|
+
[24, adding({ underline: false, doubleUnderline: false })],
|
|
149
|
+
[25, adding({ blink: false, rapidBlink: false })],
|
|
120
150
|
[27, adding({ reverse: false })],
|
|
121
151
|
[28, adding({ conceal: false })],
|
|
122
152
|
[29, adding({ strike: false })],
|
|
@@ -124,7 +154,7 @@ const SGR_OPS = new Map([
|
|
|
124
154
|
[39, adding({ color: ColorSpec.default() })],
|
|
125
155
|
[48, (style, rest) => style.add(new Style({ bgcolor: extendedColor(rest) }))],
|
|
126
156
|
[49, adding({ bgcolor: ColorSpec.default() })],
|
|
127
|
-
// Underline
|
|
157
|
+
// Underline color has no field on Style; its arguments are still spent, or
|
|
128
158
|
// `58;2;1;2;3` would read on as bold, dim and italic.
|
|
129
159
|
[58, (style, rest) => (extendedColor(rest), style)],
|
|
130
160
|
[54, adding({ frame: false, encircle: false })],
|
|
@@ -153,11 +183,52 @@ function applySgr(style, params) {
|
|
|
153
183
|
result = (SGR_OPS.get(code) ?? identity)(result, codes);
|
|
154
184
|
return result;
|
|
155
185
|
}
|
|
186
|
+
const ORIGIN = ["", NULL_STYLE];
|
|
156
187
|
const BLANK = [" ", NULL_STYLE];
|
|
188
|
+
const TAB_STOP = 8;
|
|
189
|
+
/** The column holding the cluster drawn across `column`. */
|
|
190
|
+
function leadOf(cells, column) {
|
|
191
|
+
let at = column;
|
|
192
|
+
while (at > 0 && cells[at][0] === "")
|
|
193
|
+
at -= 1;
|
|
194
|
+
return at;
|
|
195
|
+
}
|
|
196
|
+
/** A cluster across the edge before `column` goes blank, as on a terminal. */
|
|
197
|
+
function splitWide(cells, column) {
|
|
198
|
+
if (cells[column]?.[0] !== "")
|
|
199
|
+
return;
|
|
200
|
+
const lead = leadOf(cells, column);
|
|
201
|
+
for (let at = lead; at === lead || cells[at]?.[0] === ""; at += 1)
|
|
202
|
+
cells[at] = BLANK;
|
|
203
|
+
}
|
|
204
|
+
/** `cluster` drawn at `column`, and the columns the cursor moves. */
|
|
205
|
+
function draw(cells, column, cluster, style) {
|
|
206
|
+
const width = cluster === "\t" ? TAB_STOP - ((column - 1) % TAB_STOP) : cellLen(cluster);
|
|
207
|
+
if (width === 0) {
|
|
208
|
+
const lead = leadOf(cells, column - 1);
|
|
209
|
+
cells[lead] = [cells[lead][0] + cluster, cells[lead][1]];
|
|
210
|
+
return 0;
|
|
211
|
+
}
|
|
212
|
+
if (cluster === "\t" && column < cells.length) {
|
|
213
|
+
while (cells.length < column + width)
|
|
214
|
+
cells.push(BLANK);
|
|
215
|
+
return width;
|
|
216
|
+
}
|
|
217
|
+
splitWide(cells, column);
|
|
218
|
+
splitWide(cells, column + width);
|
|
219
|
+
cells[column] = [cells[0][0] + cluster, style];
|
|
220
|
+
cells[0] = ORIGIN;
|
|
221
|
+
for (let at = 1; at < width; at += 1)
|
|
222
|
+
cells[column + at] = ["", style];
|
|
223
|
+
return width;
|
|
224
|
+
}
|
|
157
225
|
/** Erase in line: the cells before the cursor go blank, the ones from it on go. */
|
|
158
226
|
function erase(cells, column, mode) {
|
|
227
|
+
splitWide(cells, column);
|
|
228
|
+
splitWide(cells, column + 1);
|
|
229
|
+
cells[0] = ORIGIN;
|
|
159
230
|
if (mode !== "0")
|
|
160
|
-
cells.fill(BLANK,
|
|
231
|
+
cells.fill(BLANK, 1, Math.min(column + 1, cells.length));
|
|
161
232
|
if (mode !== "1")
|
|
162
233
|
cells.length = Math.min(column, cells.length);
|
|
163
234
|
}
|
|
@@ -173,25 +244,32 @@ export class AnsiDecoder {
|
|
|
173
244
|
link = undefined;
|
|
174
245
|
/** One `RichText` per line of `ansi`; a final newline ends a line rather than starting one. */
|
|
175
246
|
decode(ansi) {
|
|
176
|
-
|
|
247
|
+
const lined = ansi.replace(lineBreaks(), (found) => (found.startsWith("\x1b") ? found : "\n"));
|
|
248
|
+
return (lined.match(/[^\n]*\n|[^\n]+/g) ?? []).map((line) => this.decodeLine(line.replace(/\r?\n$/, "")));
|
|
177
249
|
}
|
|
178
250
|
/**
|
|
179
251
|
* One line of output, holding no `\n`, written the way a terminal writes
|
|
180
252
|
* it: `\r` returns to the first column, and text overwrites from there.
|
|
181
253
|
*/
|
|
182
254
|
decodeLine(line) {
|
|
183
|
-
const cells = [];
|
|
184
|
-
let column =
|
|
255
|
+
const cells = [ORIGIN];
|
|
256
|
+
let column = 1;
|
|
185
257
|
for (const token of tokens(line)) {
|
|
186
258
|
switch (token.kind) {
|
|
187
259
|
case "text": {
|
|
188
260
|
const style = this.sgr.withLink(this.link);
|
|
189
|
-
for (const
|
|
190
|
-
|
|
261
|
+
for (const cluster of graphemes(token.text))
|
|
262
|
+
column += draw(cells, column, cluster, style);
|
|
191
263
|
break;
|
|
192
264
|
}
|
|
265
|
+
case "overstrike":
|
|
266
|
+
column += draw(cells, column, token.character, this.sgr.add(token.emphasis).withLink(this.link));
|
|
267
|
+
break;
|
|
193
268
|
case "return":
|
|
194
|
-
column =
|
|
269
|
+
column = 1;
|
|
270
|
+
break;
|
|
271
|
+
case "backspace":
|
|
272
|
+
column = Math.max(1, column - 1);
|
|
195
273
|
break;
|
|
196
274
|
case "erase":
|
|
197
275
|
erase(cells, column, token.mode);
|
|
@@ -205,7 +283,7 @@ export class AnsiDecoder {
|
|
|
205
283
|
}
|
|
206
284
|
}
|
|
207
285
|
const runs = [];
|
|
208
|
-
for (const [char, style] of cells) {
|
|
286
|
+
for (const [char, style] of cells.filter(([char]) => char !== "")) {
|
|
209
287
|
const last = runs.at(-1);
|
|
210
288
|
if (last?.[1].equals(style))
|
|
211
289
|
last[0] += char;
|