@promptctl/rich-js 0.24.0 → 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/THIRD-PARTY-NOTICES +29 -1
- package/dist/core/anchor.d.ts +5 -0
- package/dist/core/anchor.js +11 -4
- package/dist/core/animation.d.ts +243 -0
- package/dist/core/animation.js +375 -0
- package/dist/core/ansi.js +2 -2
- package/dist/core/box.d.ts +7 -7
- package/dist/core/cell-widths.d.ts +9 -0
- package/dist/core/cell-widths.js +140 -0
- package/dist/core/cells.d.ts +40 -24
- package/dist/core/cells.js +151 -66
- 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 +48 -43
- package/dist/core/easing.d.ts +140 -10
- package/dist/core/easing.js +328 -50
- 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 +3 -2
- package/dist/core/markup.js +16 -12
- package/dist/core/measure.js +3 -3
- 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/place.js +2 -1
- package/dist/core/pretty.js +11 -12
- package/dist/core/protocol.d.ts +136 -21
- package/dist/core/protocol.js +203 -16
- package/dist/core/render.d.ts +15 -2
- package/dist/core/render.js +27 -6
- package/dist/core/segment.d.ts +31 -6
- package/dist/core/segment.js +68 -53
- 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 +11 -13
- package/dist/core/strip.js +126 -113
- package/dist/core/style.d.ts +47 -14
- package/dist/core/style.js +74 -34
- package/dist/core/text.d.ts +5 -8
- package/dist/core/text.js +133 -133
- package/dist/core/timeline.d.ts +109 -0
- package/dist/core/timeline.js +180 -0
- package/dist/core/tween.d.ts +49 -0
- package/dist/core/tween.js +71 -0
- 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 +72 -38
- package/dist/host/input.js +3 -4
- package/dist/index.d.ts +30 -15
- package/dist/index.js +20 -6
- package/dist/node/traceback.js +4 -3
- package/dist/renderables/change.d.ts +42 -0
- package/dist/renderables/change.js +112 -0
- package/dist/renderables/columns.d.ts +1 -1
- package/dist/renderables/columns.js +3 -3
- package/dist/renderables/constrain.js +2 -1
- package/dist/renderables/effect.d.ts +155 -92
- package/dist/renderables/effect.js +203 -136
- package/dist/renderables/effects.d.ts +137 -24
- package/dist/renderables/effects.js +276 -113
- package/dist/renderables/embed.d.ts +1 -1
- package/dist/renderables/embed.js +18 -9
- package/dist/renderables/flexStrip.js +2 -1
- package/dist/renderables/frames.d.ts +45 -43
- package/dist/renderables/frames.js +57 -45
- 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 +1 -1
- package/dist/renderables/group.js +2 -2
- package/dist/renderables/html-entities.d.ts +8 -0
- package/dist/renderables/html-entities.js +386 -0
- package/dist/renderables/layout.d.ts +10 -9
- package/dist/renderables/layout.js +42 -35
- package/dist/renderables/lifecycle.d.ts +77 -0
- package/dist/renderables/lifecycle.js +132 -0
- package/dist/renderables/list-view.d.ts +2 -2
- package/dist/renderables/list-view.js +11 -8
- package/dist/renderables/live.d.ts +36 -21
- package/dist/renderables/live.js +89 -42
- package/dist/renderables/markdown-parse.d.ts +109 -0
- package/dist/renderables/markdown-parse.js +1427 -0
- package/dist/renderables/markdown-url.d.ts +19 -0
- package/dist/renderables/markdown-url.js +314 -0
- package/dist/renderables/markdown.d.ts +5 -7
- package/dist/renderables/markdown.js +356 -683
- package/dist/renderables/padding.d.ts +7 -20
- package/dist/renderables/padding.js +29 -50
- package/dist/renderables/panel.d.ts +30 -32
- package/dist/renderables/panel.js +65 -109
- package/dist/renderables/progress.d.ts +83 -32
- package/dist/renderables/progress.js +186 -90
- package/dist/renderables/progressBar.d.ts +22 -12
- package/dist/renderables/progressBar.js +33 -10
- package/dist/renderables/prompt.js +3 -2
- package/dist/renderables/python-space.d.ts +13 -0
- package/dist/renderables/python-space.js +25 -0
- package/dist/renderables/scroll.d.ts +50 -0
- package/dist/renderables/scroll.js +106 -0
- package/dist/renderables/spinner.d.ts +57 -24
- package/dist/renderables/spinner.js +101 -49
- package/dist/renderables/status.js +1 -1
- package/dist/renderables/syntax.d.ts +10 -9
- package/dist/renderables/syntax.js +5 -3
- package/dist/renderables/table.d.ts +48 -46
- package/dist/renderables/table.js +170 -208
- package/dist/renderables/traceback.d.ts +3 -3
- package/dist/renderables/traceback.js +12 -10
- package/dist/renderables/tree.d.ts +28 -25
- package/dist/renderables/tree.js +108 -58
- package/dist/renderables/viewport.d.ts +77 -38
- package/dist/renderables/viewport.js +156 -72
- 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 +7 -6
- package/dist/themes/ramp.d.ts +62 -4
- package/dist/themes/ramp.js +82 -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 +23 -14
- package/dist/widgets/focus-manager.js +34 -33
- package/dist/widgets/hit.d.ts +6 -0
- package/dist/widgets/hit.js +8 -1
- package/dist/widgets/index.d.ts +1 -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 +1 -1
- package/dist/widgets/static-item.js +6 -7
- package/dist/widgets/text-input.d.ts +53 -47
- package/dist/widgets/text-input.js +456 -399
- 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 +32 -6
- package/dist/widgets/widget-app.js +74 -27
- package/dist/widgets/widget-base.d.ts +2 -2
- package/dist/widgets/widget-base.js +29 -21
- package/package.json +9 -5
|
@@ -5,10 +5,11 @@ import type { Segment } from "../core/segment.js";
|
|
|
5
5
|
import { RichText } from "../core/text.js";
|
|
6
6
|
import { Console } from "../core/console.js";
|
|
7
7
|
import type { Style } from "../core/style.js";
|
|
8
|
-
import {
|
|
9
|
-
import { type Clock } from "../core/clock.js";
|
|
8
|
+
import type { Clock } from "../core/clock.js";
|
|
10
9
|
import { type ColumnOptions } from "./table.js";
|
|
11
|
-
import type { Renderable, RenderOptions } from "../core/protocol.js";
|
|
10
|
+
import type { FrameHost, Renderable, RenderOptions } from "../core/protocol.js";
|
|
11
|
+
import { Animation } from "../core/animation.js";
|
|
12
|
+
import type { Timing } from "../core/easing.js";
|
|
12
13
|
export interface TaskOptions {
|
|
13
14
|
total?: number;
|
|
14
15
|
start?: boolean;
|
|
@@ -22,30 +23,31 @@ export interface TaskUpdateOptions {
|
|
|
22
23
|
refresh?: boolean;
|
|
23
24
|
}
|
|
24
25
|
export interface Task {
|
|
25
|
-
id: number;
|
|
26
|
-
description: string;
|
|
27
|
-
total: number | undefined;
|
|
28
|
-
completed: number;
|
|
29
|
-
visible: boolean;
|
|
30
|
-
/** When the task started, in seconds on its `Progress`'s clock; undefined until it has. */
|
|
31
|
-
startTime: number | undefined;
|
|
26
|
+
readonly id: number;
|
|
27
|
+
readonly description: string;
|
|
28
|
+
readonly total: number | undefined;
|
|
29
|
+
readonly completed: number;
|
|
32
30
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
31
|
+
* The count a bar shows, over its `Progress`'s clock: `completed` itself,
|
|
32
|
+
* or moving to it after each update when the `Progress` was given a `fill`
|
|
33
|
+
* timing. `completed` stays the count; only the bar draws this.
|
|
35
34
|
*/
|
|
36
|
-
|
|
35
|
+
readonly fill: Animation<number>;
|
|
36
|
+
readonly visible: boolean;
|
|
37
|
+
/** When the task started, in seconds on its `Progress`'s host's time; undefined until it has, as of the frame being drawn. */
|
|
38
|
+
readonly startTime: number | undefined;
|
|
37
39
|
/**
|
|
38
40
|
* Rich's `Task.elapsed`: seconds since the task started, as of the frame
|
|
39
|
-
* being drawn — `Progress`
|
|
41
|
+
* being drawn — `Progress` takes the time once a frame and hands columns
|
|
40
42
|
* the result, so no column reads a clock. Undefined until the task starts.
|
|
41
43
|
*/
|
|
42
|
-
elapsed: number | undefined;
|
|
44
|
+
readonly elapsed: number | undefined;
|
|
43
45
|
/**
|
|
44
46
|
* Rich's `Task.finished_time`: the elapsed time the first time an update
|
|
45
47
|
* finds a started task at its total, kept from then on, so a finished task
|
|
46
48
|
* stays finished however its count moves after.
|
|
47
49
|
*/
|
|
48
|
-
finishedTime: number | undefined;
|
|
50
|
+
readonly finishedTime: number | undefined;
|
|
49
51
|
/**
|
|
50
52
|
* Rich's `Task.speed`: steps a second over the updates that moved the task
|
|
51
53
|
* in the `SPEED_ESTIMATE_PERIOD` before its latest update — measured back from
|
|
@@ -53,7 +55,7 @@ export interface Task {
|
|
|
53
55
|
* speed it had. Undefined until the task has started and two such updates lie
|
|
54
56
|
* apart in time.
|
|
55
57
|
*/
|
|
56
|
-
speed: number | undefined;
|
|
58
|
+
readonly speed: number | undefined;
|
|
57
59
|
}
|
|
58
60
|
/**
|
|
59
61
|
* One cell of a `Progress` row, made for the task on that row. Not a
|
|
@@ -82,8 +84,16 @@ export declare class BarColumn implements ProgressColumn {
|
|
|
82
84
|
readonly barWidth: number;
|
|
83
85
|
readonly tableColumn: ColumnOptions;
|
|
84
86
|
constructor(barWidth?: number);
|
|
85
|
-
/**
|
|
86
|
-
|
|
87
|
+
/**
|
|
88
|
+
* Rich's `BarColumn`: a task not yet started, or with no total to fill
|
|
89
|
+
* toward, pulses, and a pulse draws no fill. Otherwise the bar is the
|
|
90
|
+
* task's `fill` drawn at the frame's time, every cell stamped with when the
|
|
91
|
+
* fill next moves, so a host draws while it eases and the bar asks for
|
|
92
|
+
* nothing once it lands. Its colour is the task's: an ease that overshoots
|
|
93
|
+
* the total does not finish the bar, and one that dips back does not
|
|
94
|
+
* unfinish it.
|
|
95
|
+
*/
|
|
96
|
+
render(task: Task): Renderable;
|
|
87
97
|
}
|
|
88
98
|
/**
|
|
89
99
|
* Rich's `{task.percentage:>3.0f}%`: the percentage right-aligned in three
|
|
@@ -112,7 +122,7 @@ export declare class TimeRemainingColumn implements ProgressColumn {
|
|
|
112
122
|
}
|
|
113
123
|
export declare class TimeElapsedColumn implements ProgressColumn {
|
|
114
124
|
readonly tableColumn: ColumnOptions;
|
|
115
|
-
render(task: Task):
|
|
125
|
+
render(task: Task): Renderable;
|
|
116
126
|
}
|
|
117
127
|
export interface SpinnerColumnOptions {
|
|
118
128
|
/** The frame's style. Defaults to `progress.spinner`, a name its console's theme resolves. */
|
|
@@ -125,7 +135,7 @@ export interface SpinnerColumnOptions {
|
|
|
125
135
|
export declare class SpinnerColumn implements ProgressColumn {
|
|
126
136
|
readonly tableColumn: ColumnOptions;
|
|
127
137
|
private readonly _spinner;
|
|
128
|
-
private _finishedText;
|
|
138
|
+
private readonly _finishedText;
|
|
129
139
|
constructor(spinnerName?: string, options?: SpinnerColumnOptions);
|
|
130
140
|
render(task: Task): Renderable;
|
|
131
141
|
}
|
|
@@ -144,41 +154,82 @@ export declare class MofNCompleteColumn implements ProgressColumn {
|
|
|
144
154
|
constructor(options?: MofNCompleteColumnOptions);
|
|
145
155
|
render(task: Task): RichText;
|
|
146
156
|
}
|
|
147
|
-
|
|
157
|
+
/**
|
|
158
|
+
* How a `Progress` is set up. It is drawn by one host, and every time it
|
|
159
|
+
* keeps or draws at is that host's `frameTime`: by its own `Live`, which the
|
|
160
|
+
* options for that `Live` configure, or, given `drawnBy`, by another host —
|
|
161
|
+
* which then does all the drawing, so none of them apply.
|
|
162
|
+
*/
|
|
163
|
+
export type ProgressOptions = ProgressDisplayOptions & (OwnLiveOptions | DrawnByOptions);
|
|
164
|
+
interface ProgressDisplayOptions {
|
|
165
|
+
expand?: boolean;
|
|
166
|
+
/**
|
|
167
|
+
* How a bar's fill moves to a task's new count: eased over this timing
|
|
168
|
+
* from wherever it is when the count changes, so a bar advancing in bursts
|
|
169
|
+
* reads as motion. Unset, the bar jumps to the count, as Rich's does.
|
|
170
|
+
*/
|
|
171
|
+
fill?: Timing;
|
|
172
|
+
}
|
|
173
|
+
interface OwnLiveOptions {
|
|
174
|
+
drawnBy?: undefined;
|
|
175
|
+
console?: Console;
|
|
148
176
|
refreshPerSecond?: number;
|
|
149
177
|
autoRefresh?: boolean;
|
|
150
178
|
transient?: boolean;
|
|
151
|
-
expand?: boolean;
|
|
152
|
-
console?: Console;
|
|
153
179
|
/**
|
|
154
|
-
* What
|
|
155
|
-
* defaults to the platform's monotonic clock.
|
|
180
|
+
* What its own `Live` ticks on, and so what tasks are timed by — Rich's
|
|
181
|
+
* `get_time`; defaults to the platform's monotonic clock.
|
|
156
182
|
*/
|
|
157
183
|
clock?: Clock;
|
|
158
184
|
}
|
|
185
|
+
interface DrawnByOptions {
|
|
186
|
+
/**
|
|
187
|
+
* The host — a `Live`, an `App` — whose frames this `Progress` is drawn in:
|
|
188
|
+
* tasks are timed by its `frameTime`, so they hold while its timeline is
|
|
189
|
+
* paused and run back while it is reversed. Drawn in any other host's
|
|
190
|
+
* frame, the `Progress` throws.
|
|
191
|
+
*/
|
|
192
|
+
drawnBy: FrameHost;
|
|
193
|
+
console?: never;
|
|
194
|
+
refreshPerSecond?: never;
|
|
195
|
+
autoRefresh?: never;
|
|
196
|
+
transient?: never;
|
|
197
|
+
clock?: never;
|
|
198
|
+
}
|
|
159
199
|
export declare class Progress implements Renderable {
|
|
160
|
-
private _columns;
|
|
161
|
-
private _tasks;
|
|
162
|
-
private
|
|
163
|
-
private
|
|
164
|
-
|
|
165
|
-
private readonly
|
|
200
|
+
private readonly _columns;
|
|
201
|
+
private readonly _tasks;
|
|
202
|
+
private readonly _live;
|
|
203
|
+
private readonly _host;
|
|
204
|
+
/** A task's fill after its count changes at `now`, from the value it shows then. */
|
|
205
|
+
private readonly _refill;
|
|
166
206
|
readonly expand: boolean;
|
|
167
207
|
constructor(...columns: (ProgressColumn | ProgressOptions)[]);
|
|
208
|
+
/** The console its own `Live` prints above the bars through. */
|
|
168
209
|
get console(): Console;
|
|
169
210
|
/** Rich's `Progress.finished`: every task finished, and so true with none. */
|
|
170
211
|
get finished(): boolean;
|
|
171
212
|
static getDefaultColumns(): ProgressColumn[];
|
|
172
213
|
addTask(description: string, options?: TaskOptions): number;
|
|
214
|
+
private _task;
|
|
173
215
|
updateTask(taskId: number, options: TaskUpdateOptions): void;
|
|
174
216
|
startTask(taskId: number): void;
|
|
175
217
|
start(): void;
|
|
218
|
+
/**
|
|
219
|
+
* Every bar settles at its count, so the frame drawn next — its own `Live`'s
|
|
220
|
+
* final one, handed back here, or the next of the host that draws it —
|
|
221
|
+
* shows where the work got to, not a bar caught mid-ease.
|
|
222
|
+
*/
|
|
176
223
|
stop(): void;
|
|
177
224
|
refresh(): void;
|
|
225
|
+
private _ownLive;
|
|
178
226
|
render(options: RenderOptions): Iterable<Segment>;
|
|
227
|
+
private _framed;
|
|
228
|
+
private _table;
|
|
179
229
|
}
|
|
180
230
|
export declare function track<T>(iterable: Iterable<T>, options?: {
|
|
181
231
|
description?: string;
|
|
182
232
|
total?: number;
|
|
183
233
|
console?: Console;
|
|
184
234
|
}): Iterable<T>;
|
|
235
|
+
export {};
|
|
@@ -2,13 +2,16 @@
|
|
|
2
2
|
* Progress — displays continuously updated progress bars.
|
|
3
3
|
*/
|
|
4
4
|
import { RichText } from "../core/text.js";
|
|
5
|
-
import { Console } from "../core/console.js";
|
|
6
5
|
import { escape as escapeMarkup, readStr, renderStr } from "../core/markup.js";
|
|
7
6
|
import { ProgressBar } from "./progressBar.js";
|
|
8
7
|
import { Spinner } from "./spinner.js";
|
|
9
8
|
import { Live } from "./live.js";
|
|
10
|
-
import { systemClock } from "../core/clock.js";
|
|
11
9
|
import { Table } from "./table.js";
|
|
10
|
+
import { render } from "../core/protocol.js";
|
|
11
|
+
import { Animation } from "../core/animation.js";
|
|
12
|
+
import { Timeline } from "../core/timeline.js";
|
|
13
|
+
import { tween } from "../core/tween.js";
|
|
14
|
+
import { ListState, State } from "../core/state.js";
|
|
12
15
|
/**
|
|
13
16
|
* Rich's defaults for `speed_estimate_period` and its sample deque's
|
|
14
17
|
* `maxlen`: an update forgets samples older than the period, and the newest
|
|
@@ -17,28 +20,32 @@ import { Table } from "./table.js";
|
|
|
17
20
|
const SPEED_ESTIMATE_PERIOD = 30;
|
|
18
21
|
const MAX_SAMPLES = 1000;
|
|
19
22
|
/**
|
|
20
|
-
* Rich's `Progress.update` bookkeeping on its sample deque,
|
|
21
|
-
* samples older than the period as of `now`, then add this
|
|
22
|
-
* moved the task forward, keeping the newest `MAX_SAMPLES`. The samples are in
|
|
23
|
-
*
|
|
23
|
+
* Rich's `Progress.update` bookkeeping on its sample deque, as the samples it
|
|
24
|
+
* leaves: drop the samples older than the period as of `now`, then add this
|
|
25
|
+
* update's if it moved the task forward, keeping the newest `MAX_SAMPLES`. The samples are in
|
|
26
|
+
* the order the updates came, which is time order unless the host's timeline
|
|
27
|
+
* ran back between them, so the stale ones are found by time, not position.
|
|
24
28
|
*/
|
|
25
29
|
function recordSample(samples, now, moved) {
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
if (moved > 0)
|
|
29
|
-
samples.push({ timestamp: now, completed: moved });
|
|
30
|
-
samples.splice(0, samples.length - MAX_SAMPLES);
|
|
30
|
+
const fresh = samples.filter((sample) => sample.timestamp >= now - SPEED_ESTIMATE_PERIOD);
|
|
31
|
+
return [...fresh, ...(moved > 0 ? [{ timestamp: now, completed: moved }] : [])].slice(-MAX_SAMPLES);
|
|
31
32
|
}
|
|
32
33
|
/**
|
|
33
34
|
* Rich's `Task.speed` over a started task's samples: everything moved after
|
|
34
|
-
* the
|
|
35
|
-
*
|
|
35
|
+
* the earliest sample, over the time from the earliest to the latest — Rich's
|
|
36
|
+
* first and last, when time only ran forward, and never negative when it ran
|
|
37
|
+
* back. No samples, or none apart in time, is no speed.
|
|
36
38
|
*/
|
|
37
39
|
function sampleSpeed(samples) {
|
|
38
|
-
|
|
40
|
+
if (samples.length === 0)
|
|
41
|
+
return undefined;
|
|
42
|
+
const times = samples.map((sample) => sample.timestamp);
|
|
43
|
+
const earliest = Math.min(...times);
|
|
44
|
+
const span = Math.max(...times) - earliest;
|
|
39
45
|
if (span === 0)
|
|
40
46
|
return undefined;
|
|
41
|
-
|
|
47
|
+
const moved = samples.reduce((sum, sample) => sum + sample.completed, 0) - samples[times.indexOf(earliest)].completed;
|
|
48
|
+
return moved / span;
|
|
42
49
|
}
|
|
43
50
|
/**
|
|
44
51
|
* A count as a task takes it in. [LAW:single-enforcer] `addTask` and
|
|
@@ -51,6 +58,25 @@ function finiteCount(field, value) {
|
|
|
51
58
|
throw new RangeError(`progress task ${field} must be a finite number, got ${value}`);
|
|
52
59
|
return value;
|
|
53
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* `state` as of `now`, the moment a frame is drawn for: a host whose timeline
|
|
63
|
+
* runs back draws moments before what was stamped later, so a task drawn
|
|
64
|
+
* before its start has not started, one drawn before it reached its total
|
|
65
|
+
* has not finished — and finishes no sooner than it started — and its speed
|
|
66
|
+
* is taken over the updates that had come by then.
|
|
67
|
+
*/
|
|
68
|
+
function taskAt({ samples, finishedAt, ...state }, now) {
|
|
69
|
+
const startTime = state.startTime !== undefined && now >= state.startTime ? state.startTime : undefined;
|
|
70
|
+
const finishedTime = startTime !== undefined && finishedAt !== undefined && now >= finishedAt ? Math.max(0, finishedAt - startTime) : undefined;
|
|
71
|
+
const started = startTime !== undefined;
|
|
72
|
+
return {
|
|
73
|
+
...state,
|
|
74
|
+
startTime,
|
|
75
|
+
elapsed: started ? now - startTime : undefined,
|
|
76
|
+
finishedTime,
|
|
77
|
+
speed: started ? sampleSpeed(samples.filter((sample) => sample.timestamp <= now)) : undefined,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
54
80
|
/** Rich's `Task.finished`. [LAW:one-source-of-truth] The one predicate every reader of "finished" asks. */
|
|
55
81
|
function finished(task) {
|
|
56
82
|
return task.finishedTime !== undefined;
|
|
@@ -82,15 +108,22 @@ export class BarColumn {
|
|
|
82
108
|
constructor(barWidth) {
|
|
83
109
|
this.barWidth = barWidth ?? 40;
|
|
84
110
|
}
|
|
85
|
-
/**
|
|
111
|
+
/**
|
|
112
|
+
* Rich's `BarColumn`: a task not yet started, or with no total to fill
|
|
113
|
+
* toward, pulses, and a pulse draws no fill. Otherwise the bar is the
|
|
114
|
+
* task's `fill` drawn at the frame's time, every cell stamped with when the
|
|
115
|
+
* fill next moves, so a host draws while it eases and the bar asks for
|
|
116
|
+
* nothing once it lands. Its colour is the task's: an ease that overshoots
|
|
117
|
+
* the total does not finish the bar, and one that dips back does not
|
|
118
|
+
* unfinish it.
|
|
119
|
+
*/
|
|
86
120
|
render(task) {
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
total: task.
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
});
|
|
121
|
+
const total = task.total ?? 100;
|
|
122
|
+
if (task.startTime === undefined || task.total === undefined) {
|
|
123
|
+
return new ProgressBar({ total, completed: task.completed, width: this.barWidth, pulse: true });
|
|
124
|
+
}
|
|
125
|
+
const done = task.completed >= total;
|
|
126
|
+
return Animation.map(task.fill, (completed) => new ProgressBar({ total, completed, width: this.barWidth, finished: done }));
|
|
94
127
|
}
|
|
95
128
|
}
|
|
96
129
|
/**
|
|
@@ -150,11 +183,21 @@ export class TimeElapsedColumn {
|
|
|
150
183
|
tableColumn = {};
|
|
151
184
|
render(task) {
|
|
152
185
|
// Rich's `finished_time if finished else elapsed`: a finished task's clock
|
|
153
|
-
// holds where it stopped, and one never started has no time to show.
|
|
154
|
-
|
|
155
|
-
|
|
186
|
+
// holds where it stopped, and one never started has no time to show. A
|
|
187
|
+
// running one is a clock placed in the cell, so the frame says when its
|
|
188
|
+
// next second turns and a host paints it then. [LAW:one-source-of-truth]
|
|
189
|
+
if (finished(task))
|
|
190
|
+
return elapsedFace(task.finishedTime);
|
|
191
|
+
return task.startTime === undefined ? elapsedFace(undefined) : runningClock(task.startTime);
|
|
156
192
|
}
|
|
157
193
|
}
|
|
194
|
+
const elapsedFace = (seconds) => new RichText(clockFace(seconds), { style: "progress.elapsed" });
|
|
195
|
+
/**
|
|
196
|
+
* The seconds since `startTime` at the frame's time, turning over on each
|
|
197
|
+
* whole one. A clock is information and not motion, so reduced it still
|
|
198
|
+
* keeps the time: `Animation.repeat` keeps its turns.
|
|
199
|
+
*/
|
|
200
|
+
const runningClock = (startTime) => Timeline.run(Timeline.play(startTime), Animation.repeat(1, (second) => Animation.constant(elapsedFace(second))));
|
|
158
201
|
/** A time as the time columns draw it, `-:--:--` when there is none to draw. */
|
|
159
202
|
function clockFace(seconds) {
|
|
160
203
|
return seconds === undefined ? "-:--:--" : formatTime(seconds);
|
|
@@ -173,10 +216,10 @@ export class SpinnerColumn {
|
|
|
173
216
|
const finishedText = options?.finishedText ?? " ";
|
|
174
217
|
this._finishedText = typeof finishedText === "string" ? readStr(finishedText, true) : finishedText.copy();
|
|
175
218
|
}
|
|
176
|
-
// Rich's `self.spinner.render(task.get_time())`:
|
|
177
|
-
//
|
|
219
|
+
// Rich's `self.spinner.render(task.get_time())`: placed, the spinner draws
|
|
220
|
+
// at the frame's time, which is the time the task is drawn at.
|
|
178
221
|
render(task) {
|
|
179
|
-
return finished(task) ? this._finishedText : this._spinner
|
|
222
|
+
return finished(task) ? this._finishedText : this._spinner;
|
|
180
223
|
}
|
|
181
224
|
}
|
|
182
225
|
/**
|
|
@@ -219,11 +262,20 @@ function formatTime(seconds) {
|
|
|
219
262
|
}
|
|
220
263
|
export class Progress {
|
|
221
264
|
_columns;
|
|
222
|
-
|
|
223
|
-
|
|
265
|
+
// Rich's Progress adds and updates tasks between frames: each task is a
|
|
266
|
+
// record replaced whole on update, so a frame reads one task as it stood.
|
|
267
|
+
// A task's id is its place in the list, from 1, so an update finds it
|
|
268
|
+
// without copying the others.
|
|
269
|
+
_tasks = new ListState();
|
|
270
|
+
// [LAW:types-are-the-program] Its own Live, or none: a Progress another
|
|
271
|
+
// host draws has nothing of its own to start.
|
|
224
272
|
_live;
|
|
225
|
-
|
|
226
|
-
|
|
273
|
+
// [LAW:one-source-of-truth] The one source of every time this display
|
|
274
|
+
// keeps or draws at, so a task's stamps and the frames it is drawn in are
|
|
275
|
+
// readings of one host.
|
|
276
|
+
_host;
|
|
277
|
+
/** A task's fill after its count changes at `now`, from the value it shows then. */
|
|
278
|
+
_refill;
|
|
227
279
|
expand;
|
|
228
280
|
constructor(...columns) {
|
|
229
281
|
// Last arg might be options
|
|
@@ -241,25 +293,37 @@ export class Progress {
|
|
|
241
293
|
cols.push(new TextColumn("[progress.description]{task.description}"), new BarColumn(), new TaskProgressColumn(), new TimeRemainingColumn());
|
|
242
294
|
}
|
|
243
295
|
this._columns = cols;
|
|
244
|
-
this._tasks = new Map();
|
|
245
|
-
this._nextId = 1;
|
|
246
|
-
this._console = opts.console ?? new Console();
|
|
247
296
|
this.expand = opts.expand ?? false;
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
297
|
+
// [LAW:dataflow-not-control-flow] The option picks the rule once; an
|
|
298
|
+
// update always refills through it. Eased, the tween starts at the
|
|
299
|
+
// update's moment, and the fill it replaces is never asked for again, so
|
|
300
|
+
// a task keeps one animation, not its history: a frame drawn for a
|
|
301
|
+
// moment before the update — a timeline run back — finds the new tween
|
|
302
|
+
// not yet begun, at the value the old one showed.
|
|
303
|
+
const timing = opts.fill;
|
|
304
|
+
this._refill = timing === undefined ? (_from, to) => Animation.constant(to) : (from, to, now) => Timeline.run(Timeline.play(now), tween(from, to, timing));
|
|
305
|
+
if (opts.drawnBy === undefined) {
|
|
306
|
+
this._live = new Live(this, {
|
|
307
|
+
console: opts.console,
|
|
308
|
+
clock: opts.clock,
|
|
309
|
+
refreshPerSecond: opts.refreshPerSecond ?? 10,
|
|
310
|
+
autoRefresh: opts.autoRefresh,
|
|
311
|
+
transient: opts.transient,
|
|
312
|
+
});
|
|
313
|
+
this._host = this._live;
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
this._live = undefined;
|
|
317
|
+
this._host = opts.drawnBy;
|
|
318
|
+
}
|
|
256
319
|
}
|
|
320
|
+
/** The console its own `Live` prints above the bars through. */
|
|
257
321
|
get console() {
|
|
258
|
-
return this.
|
|
322
|
+
return this._ownLive("console").console;
|
|
259
323
|
}
|
|
260
324
|
/** Rich's `Progress.finished`: every task finished, and so true with none. */
|
|
261
325
|
get finished() {
|
|
262
|
-
return
|
|
326
|
+
return this._tasks.get().every((task) => finished(taskAt(task.get(), this._host.frameTime)));
|
|
263
327
|
}
|
|
264
328
|
static getDefaultColumns() {
|
|
265
329
|
return [
|
|
@@ -270,86 +334,118 @@ export class Progress {
|
|
|
270
334
|
];
|
|
271
335
|
}
|
|
272
336
|
addTask(description, options) {
|
|
273
|
-
const id = this.
|
|
274
|
-
|
|
337
|
+
const id = this._tasks.get().length + 1;
|
|
338
|
+
this._tasks.push(new State({
|
|
275
339
|
id,
|
|
276
340
|
description,
|
|
277
341
|
total: options?.total === undefined ? undefined : finiteCount("total", options.total),
|
|
278
342
|
completed: 0,
|
|
343
|
+
fill: Animation.constant(0),
|
|
279
344
|
visible: options?.visible !== false,
|
|
280
|
-
startTime: options?.start === false ? undefined : this.
|
|
281
|
-
|
|
345
|
+
startTime: options?.start === false ? undefined : this._host.frameTime,
|
|
346
|
+
finishedAt: undefined,
|
|
282
347
|
samples: [],
|
|
283
|
-
};
|
|
284
|
-
this._tasks.set(id, task);
|
|
348
|
+
}));
|
|
285
349
|
return id;
|
|
286
350
|
}
|
|
351
|
+
_task(taskId) {
|
|
352
|
+
return this._tasks.get()[taskId - 1];
|
|
353
|
+
}
|
|
287
354
|
updateTask(taskId, options) {
|
|
288
|
-
const
|
|
289
|
-
if (!
|
|
355
|
+
const held = this._task(taskId);
|
|
356
|
+
if (!held)
|
|
290
357
|
return;
|
|
358
|
+
const task = held.get();
|
|
291
359
|
// Rich's order: `advance` is added, then a given `completed` overwrites it,
|
|
292
360
|
// so `completed` wins when both come. Checked whole before any field is
|
|
293
361
|
// written, so a refused update leaves the task as it was.
|
|
362
|
+
const live = options.refresh ? this._ownLive("refresh") : undefined;
|
|
294
363
|
const advance = finiteCount("advance", options.advance ?? 0);
|
|
295
364
|
const completed = options.completed === undefined
|
|
296
365
|
? finiteCount("completed + advance", task.completed + advance)
|
|
297
366
|
: finiteCount("completed", options.completed);
|
|
298
|
-
const now = this.
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
task.completed
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
367
|
+
const now = this._host.frameTime;
|
|
368
|
+
const reached = task.startTime !== undefined && task.total !== undefined && completed >= task.total;
|
|
369
|
+
held.set({
|
|
370
|
+
...task,
|
|
371
|
+
completed,
|
|
372
|
+
// A retarget is a change of count: an update that leaves it alone leaves
|
|
373
|
+
// the ease running as it was, rather than restarting it from where it is.
|
|
374
|
+
fill: completed === task.completed ? task.fill : this._refill(task.fill.at(now), completed, now),
|
|
375
|
+
description: options.description ?? task.description,
|
|
376
|
+
visible: options.visible ?? task.visible,
|
|
377
|
+
samples: recordSample(task.samples, now, completed - task.completed),
|
|
378
|
+
// Rich's `finished_time`, kept as the moment it was reached, so a frame
|
|
379
|
+
// drawn before it — a timeline run back — finds the task not yet finished.
|
|
380
|
+
finishedAt: task.finishedAt ?? (reached ? now : undefined),
|
|
381
|
+
});
|
|
382
|
+
live?.refresh();
|
|
314
383
|
}
|
|
315
384
|
startTask(taskId) {
|
|
316
|
-
const
|
|
385
|
+
const held = this._task(taskId);
|
|
317
386
|
// Rich's `start_task`: a task already running keeps the time it started.
|
|
318
|
-
if (
|
|
319
|
-
|
|
387
|
+
if (held)
|
|
388
|
+
held.set({ ...held.get(), startTime: held.get().startTime ?? this._host.frameTime });
|
|
320
389
|
}
|
|
321
390
|
start() {
|
|
322
|
-
this.
|
|
391
|
+
this._ownLive("start").start();
|
|
323
392
|
}
|
|
393
|
+
/**
|
|
394
|
+
* Every bar settles at its count, so the frame drawn next — its own `Live`'s
|
|
395
|
+
* final one, handed back here, or the next of the host that draws it —
|
|
396
|
+
* shows where the work got to, not a bar caught mid-ease.
|
|
397
|
+
*/
|
|
324
398
|
stop() {
|
|
325
|
-
this.
|
|
399
|
+
for (const task of this._tasks.get())
|
|
400
|
+
task.set({ ...task.get(), fill: Animation.constant(task.get().completed) });
|
|
401
|
+
this._live?.stop();
|
|
326
402
|
}
|
|
327
403
|
refresh() {
|
|
328
|
-
this.
|
|
404
|
+
this._ownLive("refresh").refresh();
|
|
405
|
+
}
|
|
406
|
+
_ownLive(asked) {
|
|
407
|
+
if (this._live === undefined) {
|
|
408
|
+
throw new Error(`Progress.${asked}: this Progress has no Live of its own; it is drawn by the host it was given as drawnBy, so use that host's ${asked}`);
|
|
409
|
+
}
|
|
410
|
+
return this._live;
|
|
329
411
|
}
|
|
330
412
|
*render(options) {
|
|
331
|
-
|
|
413
|
+
const framed = this._framed(options);
|
|
414
|
+
yield* render(this._table(framed.frame.time), framed);
|
|
415
|
+
}
|
|
416
|
+
// Inside a frame, the time its host read for it: a second read here would
|
|
417
|
+
// land a moving bar's stamp a hair after the frame, a moment the host takes
|
|
418
|
+
// for one still to come. A frame is only ever this display's host's,
|
|
419
|
+
// because the tasks were stamped on that host's time and a frame of any
|
|
420
|
+
// other is drawn for a moment on a different clock. A print is drawn for no
|
|
421
|
+
// moment, so it is drawn in a frame for the one the program asks at, read
|
|
422
|
+
// as `updateTask` reads it, and every bar and spinner placed in the table
|
|
423
|
+
// draws at that moment too. [LAW:one-source-of-truth]
|
|
424
|
+
_framed(options) {
|
|
425
|
+
const { frame } = options;
|
|
426
|
+
if (frame !== undefined && frame.host !== this._host) {
|
|
427
|
+
throw new Error("a Progress was drawn in a frame of a host other than its own; give it that host as `drawnBy`");
|
|
428
|
+
}
|
|
429
|
+
return { ...options, frame: frame ?? { time: this._host.frameTime, host: this._host } };
|
|
430
|
+
}
|
|
431
|
+
// Rich's `make_tasks_table`: each column laid out as it says, at `now` on
|
|
432
|
+
// this display's clock.
|
|
433
|
+
_table(now) {
|
|
332
434
|
const table = Table.grid({ expand: this.expand });
|
|
333
435
|
for (const col of this._columns) {
|
|
334
436
|
table.addColumn(undefined, col.tableColumn);
|
|
335
437
|
}
|
|
336
|
-
// [LAW:effects-at-boundaries] One
|
|
337
|
-
//
|
|
338
|
-
|
|
339
|
-
for (const
|
|
438
|
+
// [LAW:effects-at-boundaries] One moment a frame, handed to every column
|
|
439
|
+
// as the task's `elapsed`; a column that moves places an animation, which
|
|
440
|
+
// draws at that same moment, the frame's.
|
|
441
|
+
for (const held of this._tasks.get()) {
|
|
442
|
+
const state = held.get();
|
|
340
443
|
if (!state.visible)
|
|
341
444
|
continue;
|
|
342
|
-
const
|
|
343
|
-
const started = startTime !== undefined;
|
|
344
|
-
const task = {
|
|
345
|
-
...state,
|
|
346
|
-
time: now,
|
|
347
|
-
elapsed: started ? now - startTime : undefined,
|
|
348
|
-
speed: started ? sampleSpeed(samples) : undefined,
|
|
349
|
-
};
|
|
445
|
+
const task = taskAt(state, now);
|
|
350
446
|
table.addRow(...this._columns.map((col) => col.render(task)));
|
|
351
447
|
}
|
|
352
|
-
|
|
448
|
+
return table;
|
|
353
449
|
}
|
|
354
450
|
}
|
|
355
451
|
// --- track ---
|
|
@@ -9,29 +9,39 @@ export interface ProgressBarOptions {
|
|
|
9
9
|
completed?: number;
|
|
10
10
|
width?: number;
|
|
11
11
|
/**
|
|
12
|
-
* Rich's pulse, for work whose end is not known
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* Rich's pulse, for work whose end is not known: the whole bar in its back
|
|
13
|
+
* style, with the library's `shimmer` carrying `bar.pulse` light along it,
|
|
14
|
+
* drawn at the time of the frame it is drawn in. Without it, the bar shows
|
|
15
|
+
* `completed` of `total`.
|
|
16
16
|
*/
|
|
17
|
-
pulse?:
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
pulse?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Whether the work is done, which picks `finishedStyle` over
|
|
20
|
+
* `completeStyle`: `completed` reaching `total`, unless said. A bar drawing
|
|
21
|
+
* an eased fill says it, since the fill it draws is not the count.
|
|
22
|
+
*/
|
|
23
|
+
finished?: boolean;
|
|
20
24
|
style?: string | Style;
|
|
21
25
|
completeStyle?: string | Style;
|
|
22
26
|
finishedStyle?: string | Style;
|
|
23
27
|
}
|
|
24
28
|
export declare class ProgressBar implements Renderable, Measurable {
|
|
25
|
-
|
|
26
|
-
|
|
29
|
+
private readonly _total;
|
|
30
|
+
private readonly _completed;
|
|
31
|
+
private readonly _finished;
|
|
27
32
|
readonly width: number | undefined;
|
|
28
|
-
readonly pulse:
|
|
29
|
-
readonly t: number;
|
|
30
|
-
} | undefined;
|
|
33
|
+
readonly pulse: boolean;
|
|
31
34
|
readonly style: string | Style;
|
|
32
35
|
readonly completeStyle: string | Style;
|
|
33
36
|
readonly finishedStyle: string | Style;
|
|
34
37
|
constructor(options?: ProgressBarOptions);
|
|
38
|
+
get total(): number;
|
|
39
|
+
set total(value: number);
|
|
40
|
+
get completed(): number;
|
|
41
|
+
set completed(value: number);
|
|
42
|
+
/** Whether the work is done, as given; unset, `completed` reaching `total` at the moment the bar is drawn. */
|
|
43
|
+
get finished(): boolean | undefined;
|
|
44
|
+
set finished(value: boolean | undefined);
|
|
35
45
|
render(options: RenderOptions): Iterable<Segment>;
|
|
36
46
|
/**
|
|
37
47
|
* The shimmer over this bar's back colour as `Effected` sees it drawn at
|