@promptctl/rich-js 0.24.1 → 0.25.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 -0
- package/dist/core/anchor.d.ts +5 -0
- package/dist/core/anchor.js +11 -4
- package/dist/core/animation.d.ts +175 -10
- package/dist/core/animation.js +283 -15
- package/dist/core/ansi.js +2 -2
- package/dist/core/box.d.ts +7 -7
- package/dist/core/cells.d.ts +23 -13
- package/dist/core/cells.js +58 -48
- package/dist/core/clock.d.ts +2 -2
- package/dist/core/clock.js +6 -4
- package/dist/core/color.d.ts +27 -10
- package/dist/core/color.js +21 -29
- package/dist/core/console.d.ts +28 -17
- package/dist/core/console.js +44 -39
- package/dist/core/easing.d.ts +30 -0
- package/dist/core/easing.js +65 -20
- package/dist/core/emoji.d.ts +1 -1
- package/dist/core/emoji.js +1 -2
- package/dist/core/export-html.js +2 -1
- package/dist/core/export-lines.js +3 -3
- package/dist/core/highlighter.d.ts +9 -8
- package/dist/core/highlighter.js +3 -2
- package/dist/core/json.js +2 -2
- package/dist/core/markup.js +16 -12
- package/dist/core/measure.js +2 -2
- package/dist/core/memo.d.ts +11 -2
- package/dist/core/memo.js +16 -0
- package/dist/core/oklch.js +17 -6
- package/dist/core/osc8.d.ts +1 -1
- package/dist/core/osc8.js +4 -5
- package/dist/core/paint.d.ts +2 -1
- package/dist/core/paint.js +4 -3
- package/dist/core/pattern.d.ts +21 -0
- package/dist/core/pattern.js +21 -0
- package/dist/core/pretty.js +9 -10
- package/dist/core/protocol.d.ts +100 -39
- package/dist/core/protocol.js +167 -21
- package/dist/core/render.d.ts +15 -2
- package/dist/core/render.js +25 -5
- package/dist/core/segment.d.ts +13 -5
- package/dist/core/segment.js +23 -9
- package/dist/core/spinnerData.d.ts +1 -1
- package/dist/core/state.d.ts +53 -0
- package/dist/core/state.js +82 -0
- package/dist/core/strip.d.ts +13 -16
- package/dist/core/strip.js +119 -110
- package/dist/core/style.d.ts +47 -14
- package/dist/core/style.js +57 -28
- package/dist/core/text.d.ts +5 -8
- package/dist/core/text.js +116 -124
- package/dist/core/timeline.d.ts +30 -5
- package/dist/core/timeline.js +43 -7
- package/dist/core/tween.d.ts +1 -1
- package/dist/core/tween.js +5 -3
- package/dist/core/view.d.ts +92 -0
- package/dist/core/view.js +83 -0
- package/dist/host/app.d.ts +49 -21
- package/dist/host/app.js +71 -37
- package/dist/host/input.js +3 -4
- package/dist/index.d.ts +24 -12
- package/dist/index.js +9 -4
- package/dist/node/traceback.js +4 -3
- package/dist/renderables/align.d.ts +2 -3
- package/dist/renderables/align.js +0 -3
- package/dist/renderables/change.d.ts +42 -0
- package/dist/renderables/change.js +112 -0
- package/dist/renderables/columns.d.ts +3 -4
- package/dist/renderables/columns.js +2 -7
- package/dist/renderables/constrain.d.ts +2 -3
- package/dist/renderables/constrain.js +0 -3
- package/dist/renderables/effect.d.ts +132 -54
- package/dist/renderables/effect.js +188 -115
- package/dist/renderables/effects.d.ts +122 -11
- package/dist/renderables/effects.js +238 -94
- package/dist/renderables/embed.d.ts +1 -1
- package/dist/renderables/embed.js +16 -8
- package/dist/renderables/flexStrip.d.ts +2 -3
- package/dist/renderables/flexStrip.js +0 -4
- package/dist/renderables/frames.d.ts +35 -54
- package/dist/renderables/frames.js +43 -58
- package/dist/renderables/glyph-effect.d.ts +158 -0
- package/dist/renderables/glyph-effect.js +237 -0
- package/dist/renderables/glyph-effects.d.ts +147 -0
- package/dist/renderables/glyph-effects.js +283 -0
- package/dist/renderables/group.d.ts +3 -4
- package/dist/renderables/group.js +0 -3
- package/dist/renderables/layout.d.ts +12 -12
- package/dist/renderables/layout.js +40 -46
- package/dist/renderables/lifecycle.d.ts +77 -0
- package/dist/renderables/lifecycle.js +132 -0
- package/dist/renderables/list-view.d.ts +4 -5
- package/dist/renderables/list-view.js +9 -13
- package/dist/renderables/live.d.ts +36 -21
- package/dist/renderables/live.js +88 -41
- package/dist/renderables/markdown-parse.d.ts +4 -4
- package/dist/renderables/markdown-parse.js +52 -46
- package/dist/renderables/markdown-url.js +11 -11
- package/dist/renderables/padding.d.ts +4 -5
- package/dist/renderables/padding.js +0 -3
- package/dist/renderables/panel.d.ts +14 -16
- package/dist/renderables/panel.js +6 -14
- package/dist/renderables/progress.d.ts +83 -34
- package/dist/renderables/progress.js +182 -95
- package/dist/renderables/progressBar.d.ts +22 -12
- package/dist/renderables/progressBar.js +32 -9
- package/dist/renderables/prompt.js +3 -2
- package/dist/renderables/python-space.js +2 -1
- package/dist/renderables/scroll.d.ts +50 -0
- package/dist/renderables/scroll.js +106 -0
- package/dist/renderables/spinner.d.ts +35 -14
- package/dist/renderables/spinner.js +65 -25
- package/dist/renderables/status.js +1 -1
- package/dist/renderables/syntax.d.ts +10 -9
- package/dist/renderables/syntax.js +3 -2
- package/dist/renderables/table.d.ts +34 -40
- package/dist/renderables/table.js +41 -63
- package/dist/renderables/traceback.d.ts +2 -2
- package/dist/renderables/traceback.js +6 -5
- package/dist/renderables/tree.d.ts +27 -25
- package/dist/renderables/tree.js +104 -58
- package/dist/renderables/viewport.d.ts +75 -44
- package/dist/renderables/viewport.js +145 -82
- package/dist/template-bindings/animation-funcs.d.ts +78 -9
- package/dist/template-bindings/animation-funcs.js +132 -14
- package/dist/template-bindings/index.d.ts +5 -5
- package/dist/template-bindings/index.js +5 -5
- package/dist/themes/ramp.d.ts +46 -3
- package/dist/themes/ramp.js +61 -4
- package/dist/themes/registry.js +5 -7
- package/dist/widgets/button.js +3 -2
- package/dist/widgets/checkbox.js +4 -3
- package/dist/widgets/dropdown.d.ts +3 -1
- package/dist/widgets/dropdown.js +20 -15
- package/dist/widgets/event-router.js +4 -2
- package/dist/widgets/focus-manager.d.ts +2 -0
- package/dist/widgets/focus-manager.js +6 -4
- package/dist/widgets/hit.d.ts +6 -0
- package/dist/widgets/hit.js +8 -1
- package/dist/widgets/observable.d.ts +23 -0
- package/dist/widgets/observable.js +43 -0
- package/dist/widgets/slider.d.ts +1 -1
- package/dist/widgets/slider.js +12 -3
- package/dist/widgets/static-item.d.ts +2 -3
- package/dist/widgets/static-item.js +0 -3
- package/dist/widgets/text-input.d.ts +28 -32
- package/dist/widgets/text-input.js +360 -346
- package/dist/widgets/themed-widget.js +2 -1
- package/dist/widgets/toggle.js +4 -3
- package/dist/widgets/types.d.ts +10 -4
- package/dist/widgets/types.js +8 -2
- package/dist/widgets/widget-app.d.ts +14 -4
- package/dist/widgets/widget-app.js +36 -7
- package/dist/widgets/widget-base.d.ts +2 -2
- package/dist/widgets/widget-base.js +29 -18
- package/package.json +8 -1
package/dist/core/timeline.js
CHANGED
|
@@ -32,11 +32,25 @@
|
|
|
32
32
|
* why it is a search and not the ulp walk a sum on one clock takes. A value
|
|
33
33
|
* moving at the mapped time changes at the host time itself.
|
|
34
34
|
*
|
|
35
|
+
* A stagger — a Group's children pulsing in sequence, a light walking down
|
|
36
|
+
* a Table's rows — is the same map once per child: `stagger` runs each
|
|
37
|
+
* child's animation on a timeline playing from its index times the delay,
|
|
38
|
+
* and the group changes when the soonest child does, each child's answer
|
|
39
|
+
* carried onto the group's clock by `run`. Before its turn a child is asked
|
|
40
|
+
* for a time before 0, which is its own business: a loop is shifted in
|
|
41
|
+
* phase. Tree, Table, Group and Layout take the children's frames as they
|
|
42
|
+
* take any renderables, through `Animation.map`, so none of them knows a
|
|
43
|
+
* stagger exists. [LAW:composability] A lifecycle is not staggered here: it
|
|
44
|
+
* carries its turn as `entered`, on the host's clock its `leave` and `gone`
|
|
45
|
+
* take, so moving it onto a shifted clock would lose them
|
|
46
|
+
* ([LAW:one-source-of-truth] for when it arrives).
|
|
47
|
+
*
|
|
35
48
|
* [LAW:no-ambient-temporal-coupling] Nothing here reads a clock. The host
|
|
36
49
|
* time is an argument everywhere, and a timeline is built from one the host
|
|
37
50
|
* hands in, so the same host time maps to the same animation time at one
|
|
38
51
|
* sample a second or sixty.
|
|
39
52
|
*/
|
|
53
|
+
import { Animation } from "./animation.js";
|
|
40
54
|
import { firstReaching } from "./easing.js";
|
|
41
55
|
/** Throws, naming `what`, unless `seconds` is a finite number of seconds. */
|
|
42
56
|
function finiteSeconds(what, seconds) {
|
|
@@ -81,7 +95,7 @@ function played(animation, { origin, time, rate }) {
|
|
|
81
95
|
const sum = origin + (edge - time) / rate;
|
|
82
96
|
return Number.isFinite(sum) ? firstReaching(sum, (host) => past(mapped(host), edge)) : sum;
|
|
83
97
|
};
|
|
84
|
-
return {
|
|
98
|
+
return Animation.of({
|
|
85
99
|
at: (host) => animation.at(mapped(hostTime(host))),
|
|
86
100
|
nextChange(host) {
|
|
87
101
|
const h = hostTime(host);
|
|
@@ -95,7 +109,8 @@ function played(animation, { origin, time, rate }) {
|
|
|
95
109
|
const edge = behind(t);
|
|
96
110
|
return edge === t ? h : Math.min(h, hostOf(edge));
|
|
97
111
|
},
|
|
98
|
-
|
|
112
|
+
reduced: () => played(animation.reduced(), { playing: true, origin, time, rate }),
|
|
113
|
+
});
|
|
99
114
|
}
|
|
100
115
|
export const Timeline = {
|
|
101
116
|
/** Playing from host time `origin`, at which the animation time is 0, at `rate` animation seconds a host second. */
|
|
@@ -128,17 +143,38 @@ export const Timeline = {
|
|
|
128
143
|
* host time to, changing at the host moment the mapped time reaches the
|
|
129
144
|
* animation's own answer — backwards, when it drops under where the value
|
|
130
145
|
* began. Paused, the value never changes and never did. The value is the
|
|
131
|
-
* animation's own, on no clock:
|
|
132
|
-
*
|
|
133
|
-
*
|
|
146
|
+
* animation's own, on no clock: placed, it is stamped at the frame's time
|
|
147
|
+
* (`placedRender`, `./animation.ts`) with this animation's answers, so a
|
|
148
|
+
* value's contents follow the frame's clock and not this map.
|
|
149
|
+
* Reduced, it is the animation reduced on the same timeline: what shows
|
|
150
|
+
* still follows the timeline, and only the motion is gone.
|
|
134
151
|
*/
|
|
135
152
|
run(timeline, animation) {
|
|
136
153
|
if (timeline.playing)
|
|
137
154
|
return played(animation, timeline);
|
|
138
|
-
return {
|
|
155
|
+
return Animation.of({
|
|
139
156
|
at: (host) => animation.at(Timeline.at(timeline, host)),
|
|
140
157
|
nextChange: (host) => (hostTime(host), Number.POSITIVE_INFINITY),
|
|
141
158
|
lastChange: (host) => (hostTime(host), Number.NEGATIVE_INFINITY),
|
|
142
|
-
|
|
159
|
+
reduced: () => Timeline.run(timeline, animation.reduced()),
|
|
160
|
+
});
|
|
161
|
+
},
|
|
162
|
+
/**
|
|
163
|
+
* Every one of `animations` at once, each on a timeline playing from its
|
|
164
|
+
* index times `delay` seconds after the earliest, their values in order, on
|
|
165
|
+
* a clock of the stagger's own from 0. It changes when the soonest of them
|
|
166
|
+
* does, on that clock (`Animation.all` of `run`s). A delay of 0 starts them
|
|
167
|
+
* together and a negative one starts the last at 0 and the first last.
|
|
168
|
+
* Reduced, the offsets are the cascade's choreography, which is motion: every
|
|
169
|
+
* child reduced at once, so from 0 everyone has arrived.
|
|
170
|
+
*/
|
|
171
|
+
stagger(animations, delay) {
|
|
172
|
+
finiteSeconds("Timeline.stagger delay", delay);
|
|
173
|
+
const span = finiteSeconds("Timeline.stagger delay times the last index", Math.max(0, animations.length - 1) * delay);
|
|
174
|
+
// [LAW:dataflow-not-control-flow] Whichever way the cascade runs, every
|
|
175
|
+
// origin is shifted by the lowest of them, so the earliest child plays from 0.
|
|
176
|
+
const earliest = Math.min(0, span);
|
|
177
|
+
const cascade = Animation.all(animations.map((animation, i) => Timeline.run(Timeline.play(i * delay - earliest), animation)));
|
|
178
|
+
return Animation.of({ ...cascade, reduced: () => Animation.all(animations.map((animation) => animation.reduced())) });
|
|
143
179
|
},
|
|
144
180
|
};
|
package/dist/core/tween.d.ts
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
* OKLCH, and a colour tween is a two-stop ramp over its seconds, not a
|
|
31
31
|
* second blender. [LAW:one-source-of-truth]
|
|
32
32
|
*
|
|
33
|
-
* Tier
|
|
33
|
+
* Tier 6 of `src/core/`: `./easing.ts` and `./animation.ts`, nothing else.
|
|
34
34
|
* [LAW:one-way-deps]
|
|
35
35
|
*/
|
|
36
36
|
import { Animation } from "./animation.js";
|
package/dist/core/tween.js
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
* OKLCH, and a colour tween is a two-stop ramp over its seconds, not a
|
|
31
31
|
* second blender. [LAW:one-source-of-truth]
|
|
32
32
|
*
|
|
33
|
-
* Tier
|
|
33
|
+
* Tier 6 of `src/core/`: `./easing.ts` and `./animation.ts`, nothing else.
|
|
34
34
|
* [LAW:one-way-deps]
|
|
35
35
|
*/
|
|
36
36
|
import { Animation } from "./animation.js";
|
|
@@ -55,7 +55,7 @@ export function tween(from, to, timing) {
|
|
|
55
55
|
const phase = Phase.once(0, seconds);
|
|
56
56
|
if (from === to)
|
|
57
57
|
return Animation.constant(from);
|
|
58
|
-
return {
|
|
58
|
+
return Animation.of({
|
|
59
59
|
at(t) {
|
|
60
60
|
const p = ease(phase(t));
|
|
61
61
|
return (1 - p) * from + p * to;
|
|
@@ -65,5 +65,7 @@ export function tween(from, to, timing) {
|
|
|
65
65
|
// `seconds`, or always was before it began.
|
|
66
66
|
nextChange: (t) => (t < 0 ? 0 : t < seconds ? t : Number.POSITIVE_INFINITY),
|
|
67
67
|
lastChange: (t) => (t < 0 ? Number.NEGATIVE_INFINITY : t < seconds ? t : seconds),
|
|
68
|
-
|
|
68
|
+
// A one-shot, reduced, cuts from where it starts to where it arrives.
|
|
69
|
+
reduced: () => Animation.cut(from, to),
|
|
70
|
+
});
|
|
69
71
|
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* View — what a host draws, and the frame it draws from it at a host time on
|
|
3
|
+
* its timeline: the lines, and what they say about when they change.
|
|
4
|
+
*
|
|
5
|
+
* A host — a `Live`, an `App` — reads its clock once a frame and draws its
|
|
6
|
+
* view for that moment. The view is a renderable, drawn as it is now (a
|
|
7
|
+
* table mutated in place between frames), at the time the host's `Timeline`
|
|
8
|
+
* (`./timeline.ts`) maps its clock to, written on the frame
|
|
9
|
+
* (`RenderOptions.frame`): every animation placed anywhere inside it draws
|
|
10
|
+
* at that time and stamps its cells with when it changes, on that clock
|
|
11
|
+
* (`placedRender`, `./animation.ts`). A program may hand a function for one
|
|
12
|
+
* instead, asked each frame so a view of its state is built from the state
|
|
13
|
+
* as it is then. It is handed no time, and `hostView` makes it the
|
|
14
|
+
* renderable it stands for, so a host holds one shape of view and draws it
|
|
15
|
+
* one way. [LAW:one-type-per-behavior]
|
|
16
|
+
*
|
|
17
|
+
* So a view says when it changes only through what it draws: what moves in
|
|
18
|
+
* it is an animation placed there — `Animation.continuous` for a value
|
|
19
|
+
* computed from the time — and never a time handed to the program.
|
|
20
|
+
* [LAW:one-source-of-truth] Pausing, reversing, scrubbing and speeding up
|
|
21
|
+
* what a host shows are changes to one value the host holds and not to
|
|
22
|
+
* anything it draws. What the drawn frame says about when it changes is on
|
|
23
|
+
* the timeline's clock, and `drawView` carries it back onto the host's by
|
|
24
|
+
* running the frame through the same timeline: the frame is a sample held
|
|
25
|
+
* for the intersection of its cells' holds (`Segment.lastChange`,
|
|
26
|
+
* `Segment.nextChange`) and the holds it was told that no cell carries
|
|
27
|
+
* (`RenderOptions.onHold`), and `Timeline.run` is the one place a hold is
|
|
28
|
+
* mapped onto host time, forwards, backwards or paused.
|
|
29
|
+
*
|
|
30
|
+
* What a host does with that is its own policy, and it reads it off the
|
|
31
|
+
* `DrawnView`. A view may also change without saying so — it reads state
|
|
32
|
+
* the host cannot see — so a host that polls for such a change, a `Live`'s
|
|
33
|
+
* auto-refresh, polls every frame.
|
|
34
|
+
*
|
|
35
|
+
* Tier 7 of `src/core/`: `./animation.ts`'s `Animation`, `./timeline.ts`,
|
|
36
|
+
* `./segment.ts`, `./protocol.ts` and `./style.ts`'s `Hold`, nothing else.
|
|
37
|
+
* [LAW:one-way-deps]
|
|
38
|
+
*/
|
|
39
|
+
import { type FrameHost, type Renderable, type RenderOptions } from "./protocol.js";
|
|
40
|
+
import { Segment } from "./segment.js";
|
|
41
|
+
import { Timeline } from "./timeline.js";
|
|
42
|
+
/**
|
|
43
|
+
* What a host draws: a renderable — an animation of renderables among them,
|
|
44
|
+
* or anything holding one — or a function asked for one each frame.
|
|
45
|
+
*/
|
|
46
|
+
export type View = Renderable | (() => Renderable);
|
|
47
|
+
/** `view` as the renderable a host holds: a function is asked each time it is drawn. */
|
|
48
|
+
export declare function hostView(view: View): Renderable;
|
|
49
|
+
/**
|
|
50
|
+
* A host's time, its clock read once: `now` on that clock; `epoch`, the
|
|
51
|
+
* moment on it the host began; and `timeline`, the map every view is drawn
|
|
52
|
+
* through. Handed none, a host's timeline starts as `Timeline.play(epoch)`,
|
|
53
|
+
* so a frame is drawn for the seconds since `epoch` until the timeline is
|
|
54
|
+
* paused, sought, re-rated or replaced.
|
|
55
|
+
*/
|
|
56
|
+
export interface HostTime {
|
|
57
|
+
readonly now: number;
|
|
58
|
+
readonly epoch: number;
|
|
59
|
+
readonly timeline: Timeline;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The time a frame drawn at `time` is drawn for, on the clock its cells are
|
|
63
|
+
* stamped on: the time the timeline maps `now` to. [LAW:single-enforcer]
|
|
64
|
+
* `drawView` draws at it and hands it to the frame as
|
|
65
|
+
* `RenderOptions.frame`, and a host reads it here for anything heard between
|
|
66
|
+
* frames — a key — so a moment a widget keeps and the frame it is compared
|
|
67
|
+
* against are on one clock.
|
|
68
|
+
*/
|
|
69
|
+
export declare function frameTime(time: HostTime): number;
|
|
70
|
+
/** What a frame drawn at host time `t` says about when it changes. */
|
|
71
|
+
export interface FrameSays {
|
|
72
|
+
/** The host time the frame was drawn at. */
|
|
73
|
+
readonly t: number;
|
|
74
|
+
/** The first host moment, never before `t`, at which the frame may stop showing this: `t` while it moves, `Infinity` once it never will. */
|
|
75
|
+
readonly nextChange: number;
|
|
76
|
+
}
|
|
77
|
+
/** A frame drawn from a view: its lines, and what it says. */
|
|
78
|
+
export interface DrawnView extends FrameSays {
|
|
79
|
+
readonly lines: Segment[][];
|
|
80
|
+
}
|
|
81
|
+
/** What a frame not yet drawn at host time `t` says: nothing. */
|
|
82
|
+
export declare const undrawn: (t: number) => FrameSays;
|
|
83
|
+
/**
|
|
84
|
+
* `view` drawn by `drawnBy` at `time` under `options`, its lines shaped by
|
|
85
|
+
* `draw` — the host's own render under those options, with its height and
|
|
86
|
+
* overflow — and when the frame changes on the host's clock: the hold its
|
|
87
|
+
* cells say, carried back through the timeline. [LAW:one-source-of-truth]
|
|
88
|
+
* `options` is the one place the frame reads the reader's preference for
|
|
89
|
+
* motion, handed on to `draw` with the frame: the time it is drawn for
|
|
90
|
+
* (`frameTime`) and `drawnBy`, the host whose time that is.
|
|
91
|
+
*/
|
|
92
|
+
export declare function drawView(view: Renderable, time: HostTime, drawnBy: FrameHost, options: RenderOptions, draw: (renderable: Renderable, options: RenderOptions) => Segment[][]): DrawnView;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* View — what a host draws, and the frame it draws from it at a host time on
|
|
3
|
+
* its timeline: the lines, and what they say about when they change.
|
|
4
|
+
*
|
|
5
|
+
* A host — a `Live`, an `App` — reads its clock once a frame and draws its
|
|
6
|
+
* view for that moment. The view is a renderable, drawn as it is now (a
|
|
7
|
+
* table mutated in place between frames), at the time the host's `Timeline`
|
|
8
|
+
* (`./timeline.ts`) maps its clock to, written on the frame
|
|
9
|
+
* (`RenderOptions.frame`): every animation placed anywhere inside it draws
|
|
10
|
+
* at that time and stamps its cells with when it changes, on that clock
|
|
11
|
+
* (`placedRender`, `./animation.ts`). A program may hand a function for one
|
|
12
|
+
* instead, asked each frame so a view of its state is built from the state
|
|
13
|
+
* as it is then. It is handed no time, and `hostView` makes it the
|
|
14
|
+
* renderable it stands for, so a host holds one shape of view and draws it
|
|
15
|
+
* one way. [LAW:one-type-per-behavior]
|
|
16
|
+
*
|
|
17
|
+
* So a view says when it changes only through what it draws: what moves in
|
|
18
|
+
* it is an animation placed there — `Animation.continuous` for a value
|
|
19
|
+
* computed from the time — and never a time handed to the program.
|
|
20
|
+
* [LAW:one-source-of-truth] Pausing, reversing, scrubbing and speeding up
|
|
21
|
+
* what a host shows are changes to one value the host holds and not to
|
|
22
|
+
* anything it draws. What the drawn frame says about when it changes is on
|
|
23
|
+
* the timeline's clock, and `drawView` carries it back onto the host's by
|
|
24
|
+
* running the frame through the same timeline: the frame is a sample held
|
|
25
|
+
* for the intersection of its cells' holds (`Segment.lastChange`,
|
|
26
|
+
* `Segment.nextChange`) and the holds it was told that no cell carries
|
|
27
|
+
* (`RenderOptions.onHold`), and `Timeline.run` is the one place a hold is
|
|
28
|
+
* mapped onto host time, forwards, backwards or paused.
|
|
29
|
+
*
|
|
30
|
+
* What a host does with that is its own policy, and it reads it off the
|
|
31
|
+
* `DrawnView`. A view may also change without saying so — it reads state
|
|
32
|
+
* the host cannot see — so a host that polls for such a change, a `Live`'s
|
|
33
|
+
* auto-refresh, polls every frame.
|
|
34
|
+
*
|
|
35
|
+
* Tier 7 of `src/core/`: `./animation.ts`'s `Animation`, `./timeline.ts`,
|
|
36
|
+
* `./segment.ts`, `./protocol.ts` and `./style.ts`'s `Hold`, nothing else.
|
|
37
|
+
* [LAW:one-way-deps]
|
|
38
|
+
*/
|
|
39
|
+
import { render } from "./protocol.js";
|
|
40
|
+
import { Segment } from "./segment.js";
|
|
41
|
+
import { Timeline } from "./timeline.js";
|
|
42
|
+
/** `view` as the renderable a host holds: a function is asked each time it is drawn. */
|
|
43
|
+
export function hostView(view) {
|
|
44
|
+
return typeof view === "function" ? { render: (options) => render(view(), options) } : view;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The time a frame drawn at `time` is drawn for, on the clock its cells are
|
|
48
|
+
* stamped on: the time the timeline maps `now` to. [LAW:single-enforcer]
|
|
49
|
+
* `drawView` draws at it and hands it to the frame as
|
|
50
|
+
* `RenderOptions.frame`, and a host reads it here for anything heard between
|
|
51
|
+
* frames — a key — so a moment a widget keeps and the frame it is compared
|
|
52
|
+
* against are on one clock.
|
|
53
|
+
*/
|
|
54
|
+
export function frameTime(time) {
|
|
55
|
+
return Timeline.at(time.timeline, time.now);
|
|
56
|
+
}
|
|
57
|
+
/** What a frame not yet drawn at host time `t` says: nothing. */
|
|
58
|
+
export const undrawn = (t) => ({ t, nextChange: Number.POSITIVE_INFINITY });
|
|
59
|
+
/** The hold of the frame `lines` draw, told `told` besides: the intersection of its cells' holds and it. */
|
|
60
|
+
const frameHold = (lines, told) => lines.reduce((hold, row) => ({ lastChange: Math.max(hold.lastChange, Segment.lastChange(row)), nextChange: Math.min(hold.nextChange, Segment.nextChange(row)) }), told);
|
|
61
|
+
/**
|
|
62
|
+
* `view` drawn by `drawnBy` at `time` under `options`, its lines shaped by
|
|
63
|
+
* `draw` — the host's own render under those options, with its height and
|
|
64
|
+
* overflow — and when the frame changes on the host's clock: the hold its
|
|
65
|
+
* cells say, carried back through the timeline. [LAW:one-source-of-truth]
|
|
66
|
+
* `options` is the one place the frame reads the reader's preference for
|
|
67
|
+
* motion, handed on to `draw` with the frame: the time it is drawn for
|
|
68
|
+
* (`frameTime`) and `drawnBy`, the host whose time that is.
|
|
69
|
+
*/
|
|
70
|
+
export function drawView(view, time, drawnBy, options, draw) {
|
|
71
|
+
const { now: host, timeline } = time;
|
|
72
|
+
let told = { lastChange: Number.NEGATIVE_INFINITY, nextChange: Number.POSITIVE_INFINITY };
|
|
73
|
+
const onHold = (hold) => {
|
|
74
|
+
told = { lastChange: Math.max(told.lastChange, hold.lastChange), nextChange: Math.min(told.nextChange, hold.nextChange) };
|
|
75
|
+
};
|
|
76
|
+
const lines = draw(view, { ...options, frame: { time: frameTime(time), host: drawnBy }, onHold });
|
|
77
|
+
const hold = frameHold(lines, told);
|
|
78
|
+
// The frame as the sample of the view it is, on the timeline's clock; the
|
|
79
|
+
// timeline says when the host's clock leaves its hold.
|
|
80
|
+
const sample = { at: () => lines, nextChange: () => hold.nextChange, lastChange: () => hold.lastChange, reduced: () => sample };
|
|
81
|
+
const nextChange = Timeline.run(timeline, sample).nextChange(host);
|
|
82
|
+
return { t: host, lines, nextChange };
|
|
83
|
+
}
|
package/dist/host/app.d.ts
CHANGED
|
@@ -34,8 +34,10 @@ import { type Clock, type FrameRate } from "../core/clock.js";
|
|
|
34
34
|
import { type ConsoleOptions } from "../core/console.js";
|
|
35
35
|
import { Segment } from "../core/segment.js";
|
|
36
36
|
import { type Surface } from "../core/paint.js";
|
|
37
|
-
import { type
|
|
37
|
+
import { type FrameHost } from "../core/protocol.js";
|
|
38
38
|
import type { Unsubscribe } from "../core/subscription.js";
|
|
39
|
+
import { Timeline } from "../core/timeline.js";
|
|
40
|
+
import { type View } from "../core/view.js";
|
|
39
41
|
import { type KeyPress, type ScreenMouseEvent } from "./input.js";
|
|
40
42
|
import type { TerminalHost } from "./terminal-host.js";
|
|
41
43
|
/**
|
|
@@ -44,7 +46,7 @@ import type { TerminalHost } from "./terminal-host.js";
|
|
|
44
46
|
* the program; `stopped` has handed it back for good.
|
|
45
47
|
*/
|
|
46
48
|
export type AppPhase = "idle" | "running" | "suspended" | "stopped";
|
|
47
|
-
declare const DRAW_OPTIONS: readonly ["asciiOnly", "theme", "onStyleError", "markup", "highlight", "highlighter"];
|
|
49
|
+
declare const DRAW_OPTIONS: readonly ["asciiOnly", "reducedMotion", "theme", "onStyleError", "markup", "highlight", "highlighter"];
|
|
48
50
|
type DrawOption = (typeof DRAW_OPTIONS)[number];
|
|
49
51
|
type DrawOptions = Readonly<Pick<ConsoleOptions, DrawOption>>;
|
|
50
52
|
export interface AppOptions extends DrawOptions {
|
|
@@ -52,15 +54,28 @@ export interface AppOptions extends DrawOptions {
|
|
|
52
54
|
readonly host: TerminalHost;
|
|
53
55
|
readonly surface: Surface;
|
|
54
56
|
/**
|
|
55
|
-
*
|
|
56
|
-
* app's state is drawn from the state as it is at
|
|
57
|
-
* frame
|
|
58
|
-
*
|
|
57
|
+
* What the app draws: a renderable, or a function asked for one once per
|
|
58
|
+
* frame — so a view of the app's state is drawn from the state as it is at
|
|
59
|
+
* that frame. What moves in it is an `Animation` placed there, drawn at the
|
|
60
|
+
* frame's time on the app's `timeline`, which says when it changes, so the
|
|
61
|
+
* app knows when to paint again.
|
|
59
62
|
*/
|
|
60
|
-
readonly view:
|
|
63
|
+
readonly view: View;
|
|
61
64
|
/** What the app reads the time from and ticks on. The platform's, unless given. */
|
|
62
65
|
readonly clock?: Clock;
|
|
63
|
-
/**
|
|
66
|
+
/**
|
|
67
|
+
* The map from the clock to the time the view is drawn for (`Timeline`).
|
|
68
|
+
* Playing from 0 when the host is made, at the clock's pace, unless given
|
|
69
|
+
* — so a tween in the view starts when the host is made, not when the clock
|
|
70
|
+
* began; `app.timeline` reads and replaces it.
|
|
71
|
+
*/
|
|
72
|
+
readonly timeline?: Timeline;
|
|
73
|
+
/**
|
|
74
|
+
* How often the app paints while what it shows moves every moment — a
|
|
75
|
+
* frame that says it moves. A frame held until a
|
|
76
|
+
* moment is painted at that moment, whatever the rate. 30 frames a second,
|
|
77
|
+
* unless given.
|
|
78
|
+
*/
|
|
64
79
|
readonly rate?: FrameRate;
|
|
65
80
|
/**
|
|
66
81
|
* The keys that stop the app, by `keyName`, heard before any `onKey`
|
|
@@ -87,14 +102,17 @@ type WheelTurn = Extract<ScreenMouseEvent, {
|
|
|
87
102
|
}>;
|
|
88
103
|
/** A pointer event `onMouse` hears: every one but the vertical wheel, which is `onWheel`'s. */
|
|
89
104
|
export type AppMouseEvent = Exclude<ScreenMouseEvent, WheelTurn>;
|
|
90
|
-
export declare class App {
|
|
105
|
+
export declare class App implements FrameHost {
|
|
91
106
|
private readonly host;
|
|
92
107
|
private readonly surface;
|
|
93
108
|
private readonly view;
|
|
94
|
-
private readonly
|
|
109
|
+
private readonly _clock;
|
|
110
|
+
private readonly _epoch;
|
|
111
|
+
private _timeline;
|
|
95
112
|
private _rate;
|
|
96
|
-
private readonly animations;
|
|
97
113
|
private ticking;
|
|
114
|
+
private said;
|
|
115
|
+
private waking;
|
|
98
116
|
private readonly console;
|
|
99
117
|
private _phase;
|
|
100
118
|
private _frame;
|
|
@@ -110,7 +128,18 @@ export declare class App {
|
|
|
110
128
|
private settle;
|
|
111
129
|
constructor(options: AppOptions);
|
|
112
130
|
get phase(): AppPhase;
|
|
113
|
-
/**
|
|
131
|
+
/** The clock the app reads the time from: where the host time a `Timeline` operation takes is read. */
|
|
132
|
+
get clock(): Clock;
|
|
133
|
+
/** The map from the clock to the time the view is drawn for. */
|
|
134
|
+
get timeline(): Timeline;
|
|
135
|
+
/**
|
|
136
|
+
* Replace the timeline and paint the frame for the time it now maps the
|
|
137
|
+
* clock's now to, so a seek shows where it sought, once. Paused, the frame
|
|
138
|
+
* then holds: nothing drawn for a held time changes, so the app waits on
|
|
139
|
+
* nothing until the timeline plays again, and paints when asked.
|
|
140
|
+
*/
|
|
141
|
+
set timeline(timeline: Timeline);
|
|
142
|
+
/** How often the app paints while what it shows moves every moment. */
|
|
114
143
|
get rate(): FrameRate;
|
|
115
144
|
/**
|
|
116
145
|
* Set how often the app paints: an animation under way goes on at the new
|
|
@@ -119,6 +148,14 @@ export declare class App {
|
|
|
119
148
|
set rate(rate: FrameRate);
|
|
120
149
|
/** The rows on screen now, as painted: what a pointer event lands on. */
|
|
121
150
|
get frame(): readonly (readonly Segment[])[];
|
|
151
|
+
/**
|
|
152
|
+
* The time a frame drawn now is drawn for (`RenderOptions.frame`): the
|
|
153
|
+
* clock's now, through the timeline. What a moment heard
|
|
154
|
+
* between frames — a key — is stamped with, so it is on the clock the
|
|
155
|
+
* frame is.
|
|
156
|
+
*/
|
|
157
|
+
get frameTime(): number;
|
|
158
|
+
private _time;
|
|
122
159
|
/**
|
|
123
160
|
* Take the terminal, paint the first frame, and resolve when the app stops
|
|
124
161
|
* — or reject with the error a frame threw or `fail` was given, the terminal
|
|
@@ -137,15 +174,6 @@ export declare class App {
|
|
|
137
174
|
* for as long as it ran.
|
|
138
175
|
*/
|
|
139
176
|
refresh(): void;
|
|
140
|
-
/**
|
|
141
|
-
* Paint at the app's `rate` until the returned function is called: what an
|
|
142
|
-
* effect sampled at the frame's time asks for while it moves. The app ticks
|
|
143
|
-
* while anything is animating and it holds the terminal, and stops when
|
|
144
|
-
* nothing is, so a still app paints only when asked. The animation's first
|
|
145
|
-
* frame does not wait out an interval: at one frame every two seconds, that
|
|
146
|
-
* would hold its start off screen for two.
|
|
147
|
-
*/
|
|
148
|
-
animate(): Unsubscribe;
|
|
149
177
|
/**
|
|
150
178
|
* Hand the terminal to whatever launched the program, and take it back and
|
|
151
179
|
* repaint when that resumes it. What sends the app here is the app's own —
|
package/dist/host/app.js
CHANGED
|
@@ -35,6 +35,8 @@ import { Console } from "../core/console.js";
|
|
|
35
35
|
import { Segment } from "../core/segment.js";
|
|
36
36
|
import { SurfacePainter } from "../core/paint.js";
|
|
37
37
|
import { fitHeight, render } from "../core/protocol.js";
|
|
38
|
+
import { Timeline } from "../core/timeline.js";
|
|
39
|
+
import { drawView, frameTime, hostView, undrawn } from "../core/view.js";
|
|
38
40
|
import { hostEnvironment } from "./host-environment.js";
|
|
39
41
|
import { InputParser, keyName, parseKeyName } from "./input.js";
|
|
40
42
|
// [LAW:one-source-of-truth] How a frame draws — its glyphs, the style names
|
|
@@ -43,7 +45,7 @@ import { InputParser, keyName, parseKeyName } from "./input.js";
|
|
|
43
45
|
// console declares them. This list is both the type and what is copied: the
|
|
44
46
|
// host alone decides the console's size, colours and sink, so no other
|
|
45
47
|
// property of the options object reaches it, whatever the object carries.
|
|
46
|
-
const DRAW_OPTIONS = ["asciiOnly", "theme", "onStyleError", "markup", "highlight", "highlighter"];
|
|
48
|
+
const DRAW_OPTIONS = ["asciiOnly", "reducedMotion", "theme", "onStyleError", "markup", "highlight", "highlighter"];
|
|
47
49
|
function drawOptions(options) {
|
|
48
50
|
const picked = {};
|
|
49
51
|
const copy = (key) => {
|
|
@@ -71,12 +73,19 @@ export class App {
|
|
|
71
73
|
host;
|
|
72
74
|
surface;
|
|
73
75
|
view;
|
|
74
|
-
|
|
76
|
+
_clock;
|
|
77
|
+
// The clock moment this app began: what its default timeline plays from,
|
|
78
|
+
// and what a renderable view is drawn for the time since (`HostTime`).
|
|
79
|
+
_epoch;
|
|
80
|
+
_timeline;
|
|
75
81
|
_rate;
|
|
76
|
-
// What is animating now: one token per `animate()` not yet released.
|
|
77
|
-
animations = new Set();
|
|
78
82
|
// The timer painting frames, and the rate it was started at.
|
|
79
83
|
ticking = null;
|
|
84
|
+
// What the frame on screen says about when it changes, on the clock.
|
|
85
|
+
// Before a frame, nothing.
|
|
86
|
+
said = undrawn(0);
|
|
87
|
+
// The one wait for the frame's `nextChange`, armed after each frame.
|
|
88
|
+
waking = null;
|
|
80
89
|
// [LAW:one-source-of-truth] The host is the console's whole environment:
|
|
81
90
|
// its size, its colours, where bytes go. Every frame renders with this
|
|
82
91
|
// console's options and is encoded for its destination.
|
|
@@ -98,8 +107,10 @@ export class App {
|
|
|
98
107
|
this.input = new InputParser({ key: (press) => this.key(press), mouse: (event) => this.mouse(event) }, options.host.inputChunks);
|
|
99
108
|
this.surface = options.surface;
|
|
100
109
|
this.painter = new SurfacePainter(options.surface, (bytes) => this.host.write(bytes));
|
|
101
|
-
this.view = options.view;
|
|
102
|
-
this.
|
|
110
|
+
this.view = hostView(options.view);
|
|
111
|
+
this._clock = options.clock ?? systemClock();
|
|
112
|
+
this._epoch = this._clock.now();
|
|
113
|
+
this._timeline = options.timeline ?? Timeline.play(this._epoch);
|
|
103
114
|
this._rate = options.rate ?? DEFAULT_RATE;
|
|
104
115
|
this.quitKeys = new Set((options.quitKeys ?? DEFAULT_QUIT_KEYS).map(parseKeyName));
|
|
105
116
|
this.console = new Console({ ...drawOptions(options), environment: hostEnvironment(options.host) });
|
|
@@ -107,7 +118,25 @@ export class App {
|
|
|
107
118
|
get phase() {
|
|
108
119
|
return this._phase;
|
|
109
120
|
}
|
|
110
|
-
/**
|
|
121
|
+
/** The clock the app reads the time from: where the host time a `Timeline` operation takes is read. */
|
|
122
|
+
get clock() {
|
|
123
|
+
return this._clock;
|
|
124
|
+
}
|
|
125
|
+
/** The map from the clock to the time the view is drawn for. */
|
|
126
|
+
get timeline() {
|
|
127
|
+
return this._timeline;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Replace the timeline and paint the frame for the time it now maps the
|
|
131
|
+
* clock's now to, so a seek shows where it sought, once. Paused, the frame
|
|
132
|
+
* then holds: nothing drawn for a held time changes, so the app waits on
|
|
133
|
+
* nothing until the timeline plays again, and paints when asked.
|
|
134
|
+
*/
|
|
135
|
+
set timeline(timeline) {
|
|
136
|
+
this._timeline = timeline;
|
|
137
|
+
this.refresh();
|
|
138
|
+
}
|
|
139
|
+
/** How often the app paints while what it shows moves every moment. */
|
|
111
140
|
get rate() {
|
|
112
141
|
return this._rate;
|
|
113
142
|
}
|
|
@@ -123,6 +152,18 @@ export class App {
|
|
|
123
152
|
get frame() {
|
|
124
153
|
return this._frame;
|
|
125
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* The time a frame drawn now is drawn for (`RenderOptions.frame`): the
|
|
157
|
+
* clock's now, through the timeline. What a moment heard
|
|
158
|
+
* between frames — a key — is stamped with, so it is on the clock the
|
|
159
|
+
* frame is.
|
|
160
|
+
*/
|
|
161
|
+
get frameTime() {
|
|
162
|
+
return frameTime(this._time());
|
|
163
|
+
}
|
|
164
|
+
_time() {
|
|
165
|
+
return { now: this._clock.now(), epoch: this._epoch, timeline: this._timeline };
|
|
166
|
+
}
|
|
126
167
|
/**
|
|
127
168
|
* Take the terminal, paint the first frame, and resolve when the app stops
|
|
128
169
|
* — or reject with the error a frame threw or `fail` was given, the terminal
|
|
@@ -187,24 +228,6 @@ export class App {
|
|
|
187
228
|
this.paint();
|
|
188
229
|
});
|
|
189
230
|
}
|
|
190
|
-
/**
|
|
191
|
-
* Paint at the app's `rate` until the returned function is called: what an
|
|
192
|
-
* effect sampled at the frame's time asks for while it moves. The app ticks
|
|
193
|
-
* while anything is animating and it holds the terminal, and stops when
|
|
194
|
-
* nothing is, so a still app paints only when asked. The animation's first
|
|
195
|
-
* frame does not wait out an interval: at one frame every two seconds, that
|
|
196
|
-
* would hold its start off screen for two.
|
|
197
|
-
*/
|
|
198
|
-
animate() {
|
|
199
|
-
const animation = {};
|
|
200
|
-
this.animations.add(animation);
|
|
201
|
-
this.tick();
|
|
202
|
-
this.refresh();
|
|
203
|
-
return () => {
|
|
204
|
-
this.animations.delete(animation);
|
|
205
|
-
this.tick();
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
231
|
/**
|
|
209
232
|
* Hand the terminal to whatever launched the program, and take it back and
|
|
210
233
|
* repaint when that resumes it. What sends the app here is the app's own —
|
|
@@ -350,19 +373,27 @@ export class App {
|
|
|
350
373
|
}
|
|
351
374
|
}
|
|
352
375
|
// [LAW:single-enforcer] Whether the clock is ticking, and at what rate,
|
|
353
|
-
//
|
|
354
|
-
//
|
|
355
|
-
//
|
|
356
|
-
//
|
|
376
|
+
// and whether a wake is armed for the frame's next change, follow from the
|
|
377
|
+
// phase, the rate and what the frame on screen says, and are brought in
|
|
378
|
+
// line with them here alone, after every change to any — so a timer runs
|
|
379
|
+
// exactly while a running app shows a frame that says it moves, at the
|
|
380
|
+
// rate the app has now, a wake is armed exactly while a running app shows
|
|
381
|
+
// a frame held until a moment, and none outlives the app.
|
|
382
|
+
// [LAW:one-source-of-truth] What the frame draws is the only thing that
|
|
383
|
+
// says whether the view moves.
|
|
357
384
|
tick() {
|
|
358
|
-
const
|
|
385
|
+
const running = this._phase === "running";
|
|
386
|
+
const { t, nextChange } = this.said;
|
|
387
|
+
const wanted = running && nextChange === t;
|
|
359
388
|
if (this.ticking !== null && (!wanted || this.ticking.rate.perSecond !== this._rate.perSecond)) {
|
|
360
389
|
this.ticking.stop();
|
|
361
390
|
this.ticking = null;
|
|
362
391
|
}
|
|
363
392
|
if (wanted && this.ticking === null) {
|
|
364
|
-
this.ticking = { rate: this._rate, stop: this.
|
|
393
|
+
this.ticking = { rate: this._rate, stop: this._clock.every(this._rate, () => this.paint()) };
|
|
365
394
|
}
|
|
395
|
+
this.waking?.();
|
|
396
|
+
this.waking = running && t < nextChange && nextChange < Number.POSITIVE_INFINITY ? this._clock.at(nextChange, () => this.paint()) : null;
|
|
366
397
|
}
|
|
367
398
|
// [LAW:no-silent-failure] A frame that throws ends the app with that
|
|
368
399
|
// error: the terminal is handed back, and `run` rejects with it.
|
|
@@ -380,13 +411,16 @@ export class App {
|
|
|
380
411
|
const options = this.console.options;
|
|
381
412
|
const height = this.painter.height(options.height.rows);
|
|
382
413
|
// [LAW:no-ambient-temporal-coupling] The frame owner reads the clock,
|
|
383
|
-
// once a frame; the view is handed the time as
|
|
384
|
-
|
|
385
|
-
// The app set the budget, so the app shapes what comes back: no
|
|
386
|
-
// than the terminal — a taller frame would scroll the rows the
|
|
387
|
-
// goes back over — and, as a region, exactly that deep.
|
|
388
|
-
const
|
|
414
|
+
// once a frame; the view is handed the time the timeline maps it to, as
|
|
415
|
+
// data, and what the frame says about when it changes comes back on the
|
|
416
|
+
// clock. The app set the budget, so the app shapes what comes back: no
|
|
417
|
+
// deeper than the terminal — a taller frame would scroll the rows the
|
|
418
|
+
// painter goes back over — and, as a region, exactly that deep.
|
|
419
|
+
const drawn = drawView(this.view, this._time(), this, { ...options, height }, (renderable, under) => fitHeight(Segment.splitLines(render(renderable, under)).slice(0, height.rows), height));
|
|
420
|
+
const frame = this.painter.paint(() => drawn.lines, { rows: height.rows, cols: options.maxWidth }, this.console.destination);
|
|
389
421
|
this._frame = frame;
|
|
422
|
+
this.said = drawn;
|
|
423
|
+
this.tick();
|
|
390
424
|
for (const handler of [...this.paintHandlers])
|
|
391
425
|
handler(frame);
|
|
392
426
|
}
|
package/dist/host/input.js
CHANGED
|
@@ -146,7 +146,6 @@ const M_LOWER = 0x6d; // m
|
|
|
146
146
|
const UTF8_ENCODER = new TextEncoder();
|
|
147
147
|
const UTF8_DECODER = new TextDecoder("utf-8");
|
|
148
148
|
const ASCII_DECODER = new TextDecoder("ascii");
|
|
149
|
-
const EMPTY_BYTES = new Uint8Array(0);
|
|
150
149
|
/**
|
|
151
150
|
* How long a `"split"` host's parser waits for the rest of an unfinished
|
|
152
151
|
* escape sequence before reading it as the keys it already holds. Textual's
|
|
@@ -195,7 +194,7 @@ export class InputParser {
|
|
|
195
194
|
// `Buffer` — node's Buffer extends Uint8Array, so Buffer chunks from
|
|
196
195
|
// process.stdin satisfy this type, while a browser host can emit native
|
|
197
196
|
// Uint8Array without depending on a Buffer global polyfill.
|
|
198
|
-
buffer =
|
|
197
|
+
buffer = new Uint8Array(0);
|
|
199
198
|
// [LAW:types-are-the-program] `setTimeout`/`clearTimeout` are universal
|
|
200
199
|
// across node and browser; `setImmediate` is node-only.
|
|
201
200
|
escTimer;
|
|
@@ -218,7 +217,7 @@ export class InputParser {
|
|
|
218
217
|
this.cancelEscape();
|
|
219
218
|
// The buffer is settled before the sink hears Escape, so a sink that
|
|
220
219
|
// resets the parser on it (an app stopping) leaves nothing to drain.
|
|
221
|
-
this.buffer = concatBytes(escaped ?
|
|
220
|
+
this.buffer = concatBytes(escaped ? new Uint8Array(0) : this.buffer, next);
|
|
222
221
|
try {
|
|
223
222
|
if (escaped)
|
|
224
223
|
this.sink.key(ESCAPE);
|
|
@@ -292,7 +291,7 @@ export class InputParser {
|
|
|
292
291
|
/** Drop what is buffered and any held escape sequence: the parser as constructed. */
|
|
293
292
|
reset() {
|
|
294
293
|
this.cancelEscape();
|
|
295
|
-
this.buffer =
|
|
294
|
+
this.buffer = new Uint8Array(0);
|
|
296
295
|
}
|
|
297
296
|
/** A lone ESC, the one hold a next chunk can settle before its bytes are read. */
|
|
298
297
|
holdingEscape() {
|