@promptctl/rich-js 0.18.1 → 0.20.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 +11 -8
- package/dist/core/box.d.ts +3 -1
- package/dist/core/box.js +6 -13
- package/dist/core/cells.d.ts +61 -28
- package/dist/core/cells.js +171 -124
- package/dist/core/clock.d.ts +48 -0
- package/dist/core/clock.js +42 -0
- package/dist/core/color.d.ts +99 -18
- package/dist/core/color.js +334 -146
- package/dist/core/console.d.ts +38 -7
- package/dist/core/console.js +159 -90
- package/dist/core/easing.d.ts +86 -0
- package/dist/core/easing.js +171 -0
- package/dist/core/env.d.ts +16 -0
- package/dist/core/env.js +16 -0
- package/dist/core/export-html.js +1 -4
- package/dist/core/export-lines.d.ts +22 -13
- package/dist/core/export-lines.js +37 -19
- package/dist/core/export-svg.d.ts +49 -0
- package/dist/core/export-svg.js +213 -0
- package/dist/core/fnv1a.d.ts +10 -0
- package/dist/core/fnv1a.js +17 -0
- package/dist/core/highlighter.d.ts +2 -2
- package/dist/core/highlighter.js +113 -47
- package/dist/core/json.js +1 -1
- package/dist/core/markup.d.ts +28 -7
- package/dist/core/markup.js +235 -312
- package/dist/core/measure.d.ts +3 -3
- package/dist/core/measure.js +12 -7
- package/dist/core/memo.d.ts +19 -0
- package/dist/core/memo.js +34 -0
- package/dist/core/noise.d.ts +31 -0
- package/dist/core/noise.js +94 -0
- package/dist/core/osc8.d.ts +6 -6
- package/dist/core/osc8.js +9 -21
- package/dist/core/paint.d.ts +107 -0
- package/dist/core/paint.js +161 -0
- package/dist/core/place.d.ts +1 -1
- package/dist/core/place.js +4 -6
- package/dist/core/pretty.d.ts +126 -2
- package/dist/core/pretty.js +571 -105
- package/dist/core/protocol.d.ts +43 -3
- package/dist/core/protocol.js +23 -2
- package/dist/core/render.d.ts +33 -30
- package/dist/core/render.js +43 -83
- package/dist/core/segment.d.ts +6 -5
- package/dist/core/segment.js +11 -15
- package/dist/core/selection.d.ts +12 -0
- package/dist/core/selection.js +14 -0
- package/dist/core/strip.d.ts +116 -74
- package/dist/core/strip.js +266 -217
- package/dist/core/style.d.ts +27 -7
- package/dist/core/style.js +106 -41
- package/dist/core/text.d.ts +43 -16
- package/dist/core/text.js +180 -59
- package/dist/core/wrap.d.ts +5 -0
- package/dist/core/wrap.js +16 -3
- package/dist/host/app.d.ts +95 -17
- package/dist/host/app.js +201 -61
- package/dist/host/index.d.ts +4 -1
- package/dist/host/index.js +1 -0
- package/dist/host/input.d.ts +84 -0
- package/dist/host/input.js +498 -0
- package/dist/host/terminal-host.d.ts +15 -2
- package/dist/host/terminal-host.js +2 -0
- package/dist/index.d.ts +26 -14
- package/dist/index.js +17 -7
- package/dist/node/prompt.d.ts +13 -0
- package/dist/node/prompt.js +30 -9
- package/dist/node/save.d.ts +4 -3
- package/dist/node/save.js +6 -3
- package/dist/node/terminal-host.d.ts +10 -2
- package/dist/node/terminal-host.js +7 -0
- package/dist/node/traceback.d.ts +2 -1
- package/dist/node/traceback.js +3 -39
- package/dist/renderables/align.js +2 -9
- package/dist/renderables/columns.d.ts +45 -29
- package/dist/renderables/columns.js +142 -103
- package/dist/renderables/constrain.js +6 -12
- package/dist/renderables/effect.d.ts +113 -0
- package/dist/renderables/effect.js +182 -0
- package/dist/renderables/effects.d.ts +252 -0
- package/dist/renderables/effects.js +565 -0
- package/dist/renderables/embed.d.ts +94 -31
- package/dist/renderables/embed.js +141 -45
- package/dist/renderables/group.d.ts +6 -3
- package/dist/renderables/group.js +5 -0
- package/dist/renderables/layout.d.ts +6 -23
- package/dist/renderables/layout.js +24 -113
- package/dist/renderables/list-view.d.ts +42 -0
- package/dist/renderables/list-view.js +78 -0
- package/dist/renderables/live.d.ts +40 -16
- package/dist/renderables/live.js +96 -83
- package/dist/renderables/markdown.d.ts +16 -2
- package/dist/renderables/markdown.js +521 -118
- package/dist/renderables/padding.d.ts +22 -1
- package/dist/renderables/padding.js +30 -24
- package/dist/renderables/panel.d.ts +31 -33
- package/dist/renderables/panel.js +137 -141
- package/dist/renderables/progress.d.ts +110 -16
- package/dist/renderables/progress.js +212 -81
- package/dist/renderables/progressBar.d.ts +24 -4
- package/dist/renderables/progressBar.js +78 -23
- package/dist/renderables/prompt.d.ts +37 -15
- package/dist/renderables/prompt.js +125 -85
- package/dist/renderables/ratio.d.ts +123 -0
- package/dist/renderables/ratio.js +213 -0
- package/dist/renderables/rule.d.ts +3 -2
- package/dist/renderables/rule.js +50 -52
- package/dist/renderables/spinner.d.ts +28 -2
- package/dist/renderables/spinner.js +52 -19
- package/dist/renderables/status.d.ts +13 -7
- package/dist/renderables/status.js +15 -29
- package/dist/renderables/syntax.d.ts +37 -7
- package/dist/renderables/syntax.js +134 -83
- package/dist/renderables/table.d.ts +58 -20
- package/dist/renderables/table.js +373 -197
- package/dist/renderables/traceback.d.ts +5 -4
- package/dist/renderables/traceback.js +239 -68
- package/dist/renderables/tree.d.ts +10 -2
- package/dist/renderables/tree.js +61 -34
- package/dist/renderables/viewport.d.ts +30 -0
- package/dist/renderables/viewport.js +61 -7
- package/dist/template-bindings/color-funcs.d.ts +31 -9
- package/dist/template-bindings/color-funcs.js +40 -14
- package/dist/template-bindings/helpers.d.ts +4 -2
- package/dist/template-bindings/helpers.js +5 -20
- package/dist/template-bindings/index.d.ts +29 -17
- package/dist/template-bindings/index.js +36 -20
- package/dist/template-bindings/palette-funcs.d.ts +3 -1
- package/dist/template-bindings/palette-funcs.js +11 -7
- package/dist/template-bindings/style-funcs.js +4 -2
- package/dist/themes/buildPalette.d.ts +24 -3
- package/dist/themes/buildPalette.js +52 -8
- package/dist/themes/colorMath.d.ts +29 -17
- package/dist/themes/colorMath.js +74 -48
- package/dist/themes/colorRef.d.ts +2 -17
- package/dist/themes/colorRef.js +13 -31
- package/dist/themes/palette.d.ts +24 -0
- package/dist/themes/palette.js +46 -3
- package/dist/themes/ramp.d.ts +21 -34
- package/dist/themes/ramp.js +23 -42
- package/dist/themes/registry.js +39 -28
- package/dist/widgets/button.d.ts +2 -6
- package/dist/widgets/button.js +9 -40
- package/dist/widgets/checkbox.d.ts +2 -5
- package/dist/widgets/checkbox.js +9 -31
- package/dist/widgets/dropdown.d.ts +2 -5
- package/dist/widgets/dropdown.js +17 -51
- package/dist/widgets/event-router.d.ts +31 -40
- package/dist/widgets/event-router.js +52 -483
- package/dist/widgets/index.d.ts +2 -1
- package/dist/widgets/ink.d.ts +22 -0
- package/dist/widgets/ink.js +31 -0
- package/dist/widgets/slider.d.ts +2 -5
- package/dist/widgets/slider.js +11 -34
- package/dist/widgets/text-input.d.ts +19 -10
- package/dist/widgets/text-input.js +156 -147
- package/dist/widgets/themed-widget.d.ts +20 -0
- package/dist/widgets/themed-widget.js +89 -0
- package/dist/widgets/toggle.d.ts +2 -5
- package/dist/widgets/toggle.js +8 -31
- package/dist/widgets/types.d.ts +8 -25
- package/dist/widgets/types.js +8 -0
- package/dist/widgets/widget-app.d.ts +24 -7
- package/dist/widgets/widget-app.js +35 -18
- package/dist/widgets/widget-base.d.ts +2 -0
- package/dist/widgets/widget-base.js +9 -2
- package/package.json +31 -17
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Progress — displays continuously updated progress bars.
|
|
3
3
|
*/
|
|
4
|
-
import { Segment } from "../core/segment.js";
|
|
4
|
+
import type { Segment } from "../core/segment.js";
|
|
5
|
+
import { RichText } from "../core/text.js";
|
|
5
6
|
import { Console } from "../core/console.js";
|
|
7
|
+
import type { Style } from "../core/style.js";
|
|
8
|
+
import { ProgressBar } from "./progressBar.js";
|
|
9
|
+
import { type Clock } from "../core/clock.js";
|
|
10
|
+
import { type ColumnOptions } from "./table.js";
|
|
6
11
|
import type { Renderable, RenderOptions } from "../core/protocol.js";
|
|
7
12
|
export interface TaskOptions {
|
|
8
13
|
total?: number;
|
|
@@ -16,45 +21,128 @@ export interface TaskUpdateOptions {
|
|
|
16
21
|
visible?: boolean;
|
|
17
22
|
refresh?: boolean;
|
|
18
23
|
}
|
|
19
|
-
interface Task {
|
|
24
|
+
export interface Task {
|
|
20
25
|
id: number;
|
|
21
26
|
description: string;
|
|
22
27
|
total: number | undefined;
|
|
23
28
|
completed: number;
|
|
24
|
-
started: boolean;
|
|
25
29
|
visible: boolean;
|
|
26
|
-
|
|
27
|
-
|
|
30
|
+
/** When the task started, in seconds on its `Progress`'s clock; undefined until it has. */
|
|
31
|
+
startTime: number | undefined;
|
|
32
|
+
/**
|
|
33
|
+
* Rich's `Task.get_time()`: the moment the frame being drawn is drawn at, in
|
|
34
|
+
* seconds on its `Progress`'s clock, so a column animates without reading one.
|
|
35
|
+
*/
|
|
36
|
+
time: number;
|
|
37
|
+
/**
|
|
38
|
+
* Rich's `Task.elapsed`: seconds since the task started, as of the frame
|
|
39
|
+
* being drawn — `Progress` reads its clock once a frame and hands columns
|
|
40
|
+
* the result, so no column reads a clock. Undefined until the task starts.
|
|
41
|
+
*/
|
|
42
|
+
elapsed: number | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* Rich's `Task.finished_time`: the elapsed time the first time an update
|
|
45
|
+
* finds a started task at its total, kept from then on, so a finished task
|
|
46
|
+
* stays finished however its count moves after.
|
|
47
|
+
*/
|
|
48
|
+
finishedTime: number | undefined;
|
|
49
|
+
/**
|
|
50
|
+
* Rich's `Task.speed`: steps a second over the updates that moved the task
|
|
51
|
+
* in the `SPEED_ESTIMATE_PERIOD` before its latest update — measured back from
|
|
52
|
+
* that update, not from now, so a task that stops being updated keeps the
|
|
53
|
+
* speed it had. Undefined until the task has started and two such updates lie
|
|
54
|
+
* apart in time.
|
|
55
|
+
*/
|
|
56
|
+
speed: number | undefined;
|
|
28
57
|
}
|
|
29
|
-
|
|
30
|
-
|
|
58
|
+
/**
|
|
59
|
+
* One cell of a `Progress` row, made for the task on that row. Not a
|
|
60
|
+
* `Renderable`: a column has nothing to draw without a task, as the
|
|
61
|
+
* reference's `ProgressColumn` is a callable of one. What it returns is the
|
|
62
|
+
* cell's content, which the progress grid lays out like any table cell's —
|
|
63
|
+
* width, wrapping and justify are decided once, in the cell, as Rich's
|
|
64
|
+
* `make_tasks_table` decides them. [LAW:single-enforcer]
|
|
65
|
+
*/
|
|
66
|
+
export interface ProgressColumn {
|
|
67
|
+
/**
|
|
68
|
+
* The grid column this one is laid out in, Rich's `get_table_column()`:
|
|
69
|
+
* whether its cell may wrap, how it justifies, its share of the width.
|
|
70
|
+
*/
|
|
71
|
+
readonly tableColumn: ColumnOptions;
|
|
72
|
+
render(task: Task): Renderable;
|
|
31
73
|
}
|
|
32
74
|
export declare class TextColumn implements ProgressColumn {
|
|
33
75
|
readonly format: string;
|
|
76
|
+
/** Text is cut, never wrapped, as Rich's `TextColumn` defaults to `Column(no_wrap=True)`. */
|
|
77
|
+
readonly tableColumn: ColumnOptions;
|
|
34
78
|
constructor(format?: string);
|
|
35
|
-
render(
|
|
79
|
+
render(task: Task): RichText;
|
|
36
80
|
}
|
|
37
81
|
export declare class BarColumn implements ProgressColumn {
|
|
38
82
|
readonly barWidth: number;
|
|
83
|
+
readonly tableColumn: ColumnOptions;
|
|
39
84
|
constructor(barWidth?: number);
|
|
40
|
-
|
|
85
|
+
/** Rich's `BarColumn`: a task not yet started, or with no total to fill toward, pulses. */
|
|
86
|
+
render(task: Task): ProgressBar;
|
|
41
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Rich's `{task.percentage:>3.0f}%`: the percentage right-aligned in three
|
|
90
|
+
* cells, so the column is four wide from 0% to 100% and the row does not
|
|
91
|
+
* shift as it counts. A task with no total shows nothing, Rich's
|
|
92
|
+
* `text_format_no_percentage`.
|
|
93
|
+
*/
|
|
42
94
|
export declare class TaskProgressColumn implements ProgressColumn {
|
|
43
|
-
|
|
95
|
+
/** A `TextColumn` in Rich, and laid out as one. */
|
|
96
|
+
readonly tableColumn: ColumnOptions;
|
|
97
|
+
render(task: Task): RichText;
|
|
98
|
+
}
|
|
99
|
+
export interface TimeRemainingColumnOptions {
|
|
100
|
+
/**
|
|
101
|
+
* Once the task is finished, draw the time it took, styled
|
|
102
|
+
* `progress.elapsed`, in place of the zero left to go: Rich's
|
|
103
|
+
* `elapsed_when_finished`. Defaults to false.
|
|
104
|
+
*/
|
|
105
|
+
elapsedWhenFinished?: boolean;
|
|
44
106
|
}
|
|
45
107
|
export declare class TimeRemainingColumn implements ProgressColumn {
|
|
46
|
-
|
|
108
|
+
readonly tableColumn: ColumnOptions;
|
|
109
|
+
readonly elapsedWhenFinished: boolean;
|
|
110
|
+
constructor(options?: TimeRemainingColumnOptions);
|
|
111
|
+
render(task: Task): RichText;
|
|
47
112
|
}
|
|
48
113
|
export declare class TimeElapsedColumn implements ProgressColumn {
|
|
49
|
-
|
|
114
|
+
readonly tableColumn: ColumnOptions;
|
|
115
|
+
render(task: Task): RichText;
|
|
116
|
+
}
|
|
117
|
+
export interface SpinnerColumnOptions {
|
|
118
|
+
/** The frame's style. Defaults to `progress.spinner`, a name its console's theme resolves. */
|
|
119
|
+
style?: string | Style;
|
|
120
|
+
/** Multiplies the spinner's frame rate. Defaults to 1. */
|
|
121
|
+
speed?: number;
|
|
122
|
+
/** Drawn in place of the frame once the task reaches its total; a string is markup. Defaults to `" "`. */
|
|
123
|
+
finishedText?: string | RichText;
|
|
50
124
|
}
|
|
51
125
|
export declare class SpinnerColumn implements ProgressColumn {
|
|
126
|
+
readonly tableColumn: ColumnOptions;
|
|
52
127
|
private _spinner;
|
|
53
|
-
|
|
54
|
-
|
|
128
|
+
private _finishedText;
|
|
129
|
+
constructor(spinnerName?: string, options?: SpinnerColumnOptions);
|
|
130
|
+
render(task: Task): Renderable;
|
|
131
|
+
}
|
|
132
|
+
export interface MofNCompleteColumnOptions {
|
|
133
|
+
/** Drawn between the two counts: Rich's `separator`. Defaults to `"/"`. */
|
|
134
|
+
separator?: string;
|
|
55
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* Rich's `MofNCompleteColumn`: both counts as whole numbers, the completed one
|
|
138
|
+
* padded to the total's width so the column holds still as it counts up to its
|
|
139
|
+
* total, styled `progress.download`.
|
|
140
|
+
*/
|
|
56
141
|
export declare class MofNCompleteColumn implements ProgressColumn {
|
|
57
|
-
|
|
142
|
+
readonly tableColumn: ColumnOptions;
|
|
143
|
+
readonly separator: string;
|
|
144
|
+
constructor(options?: MofNCompleteColumnOptions);
|
|
145
|
+
render(task: Task): RichText;
|
|
58
146
|
}
|
|
59
147
|
export interface ProgressOptions {
|
|
60
148
|
refreshPerSecond?: number;
|
|
@@ -62,6 +150,11 @@ export interface ProgressOptions {
|
|
|
62
150
|
transient?: boolean;
|
|
63
151
|
expand?: boolean;
|
|
64
152
|
console?: Console;
|
|
153
|
+
/**
|
|
154
|
+
* What tasks are timed by and frames are drawn on, Rich's `get_time`;
|
|
155
|
+
* defaults to the platform's monotonic clock. Handed on to the `Live`.
|
|
156
|
+
*/
|
|
157
|
+
clock?: Clock;
|
|
65
158
|
}
|
|
66
159
|
export declare class Progress implements Renderable {
|
|
67
160
|
private _columns;
|
|
@@ -69,9 +162,11 @@ export declare class Progress implements Renderable {
|
|
|
69
162
|
private _nextId;
|
|
70
163
|
private _live;
|
|
71
164
|
private _console;
|
|
165
|
+
private readonly _clock;
|
|
72
166
|
readonly expand: boolean;
|
|
73
167
|
constructor(...columns: (ProgressColumn | ProgressOptions)[]);
|
|
74
168
|
get console(): Console;
|
|
169
|
+
/** Rich's `Progress.finished`: every task finished, and so true with none. */
|
|
75
170
|
get finished(): boolean;
|
|
76
171
|
static getDefaultColumns(): ProgressColumn[];
|
|
77
172
|
addTask(description: string, options?: TaskOptions): number;
|
|
@@ -87,4 +182,3 @@ export declare function track<T>(iterable: Iterable<T>, options?: {
|
|
|
87
182
|
total?: number;
|
|
88
183
|
console?: Console;
|
|
89
184
|
}): Iterable<T>;
|
|
90
|
-
export {};
|
|
@@ -1,100 +1,218 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Progress — displays continuously updated progress bars.
|
|
3
3
|
*/
|
|
4
|
-
import { Segment } from "../core/segment.js";
|
|
5
4
|
import { RichText } from "../core/text.js";
|
|
6
5
|
import { Console } from "../core/console.js";
|
|
7
|
-
import { escape as escapeMarkup } from "../core/markup.js";
|
|
8
|
-
import { embeddedText } from "./embed.js";
|
|
6
|
+
import { escape as escapeMarkup, readStr, renderStr } from "../core/markup.js";
|
|
9
7
|
import { ProgressBar } from "./progressBar.js";
|
|
10
8
|
import { Spinner } from "./spinner.js";
|
|
11
9
|
import { Live } from "./live.js";
|
|
10
|
+
import { systemClock } from "../core/clock.js";
|
|
12
11
|
import { Table } from "./table.js";
|
|
13
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Rich's defaults for `speed_estimate_period` and its sample deque's
|
|
14
|
+
* `maxlen`: an update forgets samples older than the period, and the newest
|
|
15
|
+
* thousand are all a task keeps.
|
|
16
|
+
*/
|
|
17
|
+
const SPEED_ESTIMATE_PERIOD = 30;
|
|
18
|
+
const MAX_SAMPLES = 1000;
|
|
19
|
+
/**
|
|
20
|
+
* Rich's `Progress.update` bookkeeping on its sample deque, in place: drop the
|
|
21
|
+
* samples older than the period as of `now`, then add this update's if it
|
|
22
|
+
* moved the task forward, keeping the newest `MAX_SAMPLES`. The samples are in
|
|
23
|
+
* time order, so the stale ones are a prefix.
|
|
24
|
+
*/
|
|
25
|
+
function recordSample(samples, now, moved) {
|
|
26
|
+
const firstFresh = samples.findIndex((sample) => sample.timestamp >= now - SPEED_ESTIMATE_PERIOD);
|
|
27
|
+
samples.splice(0, firstFresh === -1 ? samples.length : firstFresh);
|
|
28
|
+
if (moved > 0)
|
|
29
|
+
samples.push({ timestamp: now, completed: moved });
|
|
30
|
+
samples.splice(0, samples.length - MAX_SAMPLES);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Rich's `Task.speed` over a started task's samples: everything moved after
|
|
34
|
+
* the first sample, over the time from the first to the last. No samples, or
|
|
35
|
+
* none apart in time, is no speed.
|
|
36
|
+
*/
|
|
37
|
+
function sampleSpeed(samples) {
|
|
38
|
+
const span = (samples.at(-1)?.timestamp ?? 0) - (samples[0]?.timestamp ?? 0);
|
|
39
|
+
if (span === 0)
|
|
40
|
+
return undefined;
|
|
41
|
+
return samples.slice(1).reduce((sum, sample) => sum + sample.completed, 0) / span;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* A count as a task takes it in. [LAW:single-enforcer] `addTask` and
|
|
45
|
+
* `updateTask` are where counts enter, so a NaN or an Infinity is refused there,
|
|
46
|
+
* with the caller's stack, rather than drawn as `NaN%` by one column and thrown
|
|
47
|
+
* on a refresh tick by another.
|
|
48
|
+
*/
|
|
49
|
+
function finiteCount(field, value) {
|
|
50
|
+
if (!Number.isFinite(value))
|
|
51
|
+
throw new RangeError(`progress task ${field} must be a finite number, got ${value}`);
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
/** Rich's `Task.finished`. [LAW:one-source-of-truth] The one predicate every reader of "finished" asks. */
|
|
55
|
+
function finished(task) {
|
|
56
|
+
return task.finishedTime !== undefined;
|
|
57
|
+
}
|
|
14
58
|
export class TextColumn {
|
|
15
59
|
format;
|
|
60
|
+
/** Text is cut, never wrapped, as Rich's `TextColumn` defaults to `Column(no_wrap=True)`. */
|
|
61
|
+
tableColumn = { noWrap: true };
|
|
16
62
|
constructor(format) {
|
|
17
63
|
this.format = format ?? "{task.description}";
|
|
18
64
|
}
|
|
19
|
-
|
|
65
|
+
render(task) {
|
|
20
66
|
// [LAW:single-enforcer] Format strings flow through the markup parser so
|
|
21
67
|
// tags like `[progress.description]` become Style spans, not literal text.
|
|
22
68
|
// Task descriptions are escaped first to prevent injection of stray tags.
|
|
23
|
-
const description = escapeMarkup(task
|
|
69
|
+
const description = escapeMarkup(task.description);
|
|
24
70
|
// Use a callback so `$&`/`$1`/`$$` in the task description aren't
|
|
25
71
|
// reinterpreted by String.replace as replacement patterns.
|
|
26
72
|
const formatted = this.format.replace(/\{task\.description\}/g, () => description);
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
|
|
73
|
+
// Markup whatever the console says, and never highlighted: Rich's
|
|
74
|
+
// `TextColumn` reads its format as markup on its own `markup=True` and
|
|
75
|
+
// highlights only with a highlighter it was given, not the console's.
|
|
76
|
+
return renderStr(formatted, { markup: true });
|
|
30
77
|
}
|
|
31
78
|
}
|
|
32
79
|
export class BarColumn {
|
|
33
80
|
barWidth;
|
|
81
|
+
tableColumn = {};
|
|
34
82
|
constructor(barWidth) {
|
|
35
83
|
this.barWidth = barWidth ?? 40;
|
|
36
84
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
85
|
+
/** Rich's `BarColumn`: a task not yet started, or with no total to fill toward, pulses. */
|
|
86
|
+
render(task) {
|
|
87
|
+
const pulsing = task.startTime === undefined || task.total === undefined;
|
|
88
|
+
return new ProgressBar({
|
|
89
|
+
total: task.total ?? 100,
|
|
90
|
+
completed: task.completed,
|
|
41
91
|
width: this.barWidth,
|
|
92
|
+
pulse: pulsing ? { t: task.time } : undefined,
|
|
42
93
|
});
|
|
43
|
-
yield* bar.render(_options);
|
|
44
94
|
}
|
|
45
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Rich's `{task.percentage:>3.0f}%`: the percentage right-aligned in three
|
|
98
|
+
* cells, so the column is four wide from 0% to 100% and the row does not
|
|
99
|
+
* shift as it counts. A task with no total shows nothing, Rich's
|
|
100
|
+
* `text_format_no_percentage`.
|
|
101
|
+
*/
|
|
46
102
|
export class TaskProgressColumn {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
103
|
+
/** A `TextColumn` in Rich, and laid out as one. */
|
|
104
|
+
tableColumn = { noWrap: true };
|
|
105
|
+
render(task) {
|
|
106
|
+
const text = task.total === undefined ? "" : `${String(roundHalfEven(percentage(task))).padStart(3)}%`;
|
|
107
|
+
return new RichText(text, { style: "progress.percentage" });
|
|
52
108
|
}
|
|
53
109
|
}
|
|
110
|
+
/** Rich's `Task.percentage`: 0 without a total, and clamped to [0, 100]. */
|
|
111
|
+
function percentage(task) {
|
|
112
|
+
return task.total ? Math.min(100, Math.max(0, (task.completed / task.total) * 100)) : 0;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Python's `format(x, ".0f")`: the nearest integer to the double's exact
|
|
116
|
+
* value, a tie going to the even one. `Math.round` is exact too but sends a
|
|
117
|
+
* tie up, so only a tie is corrected.
|
|
118
|
+
*/
|
|
119
|
+
function roundHalfEven(x) {
|
|
120
|
+
const up = Math.round(x);
|
|
121
|
+
return up - x === 0.5 && up % 2 !== 0 ? up - 1 : up;
|
|
122
|
+
}
|
|
54
123
|
export class TimeRemainingColumn {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
const
|
|
63
|
-
const
|
|
64
|
-
|
|
124
|
+
tableColumn = {};
|
|
125
|
+
elapsedWhenFinished;
|
|
126
|
+
constructor(options) {
|
|
127
|
+
this.elapsedWhenFinished = options?.elapsedWhenFinished ?? false;
|
|
128
|
+
}
|
|
129
|
+
render(task) {
|
|
130
|
+
const took = this.elapsedWhenFinished && finished(task) ? task.finishedTime : undefined;
|
|
131
|
+
const time = took ?? timeRemaining(task);
|
|
132
|
+
const style = took === undefined ? "progress.remaining" : "progress.elapsed";
|
|
133
|
+
// A task with no total has no time to estimate, and Rich draws its cell empty.
|
|
134
|
+
return new RichText(task.total === undefined ? "" : clockFace(time), { style });
|
|
65
135
|
}
|
|
66
136
|
}
|
|
137
|
+
/**
|
|
138
|
+
* Rich's `Task.time_remaining`: nothing left once the task is finished,
|
|
139
|
+
* however long ago that was, unknown until it has a speed against a total,
|
|
140
|
+
* and otherwise whole seconds, rounded up.
|
|
141
|
+
*/
|
|
142
|
+
function timeRemaining(task) {
|
|
143
|
+
if (finished(task))
|
|
144
|
+
return 0;
|
|
145
|
+
if (!task.speed || task.total === undefined)
|
|
146
|
+
return undefined;
|
|
147
|
+
return Math.ceil((task.total - task.completed) / task.speed);
|
|
148
|
+
}
|
|
67
149
|
export class TimeElapsedColumn {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
const elapsed = (Date.now() - task.startTime) / 1000;
|
|
75
|
-
yield new Segment(formatTime(elapsed), style);
|
|
150
|
+
tableColumn = {};
|
|
151
|
+
render(task) {
|
|
152
|
+
// 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
|
+
const time = finished(task) ? task.finishedTime : task.elapsed;
|
|
155
|
+
return new RichText(clockFace(time), { style: "progress.elapsed" });
|
|
76
156
|
}
|
|
77
157
|
}
|
|
158
|
+
/** A time as the time columns draw it, `-:--:--` when there is none to draw. */
|
|
159
|
+
function clockFace(seconds) {
|
|
160
|
+
return seconds === undefined ? "-:--:--" : formatTime(seconds);
|
|
161
|
+
}
|
|
78
162
|
export class SpinnerColumn {
|
|
163
|
+
tableColumn = {};
|
|
79
164
|
_spinner;
|
|
80
|
-
|
|
81
|
-
|
|
165
|
+
_finishedText;
|
|
166
|
+
constructor(spinnerName, options) {
|
|
167
|
+
this._spinner = new Spinner(spinnerName, "", {
|
|
168
|
+
style: options?.style ?? "progress.spinner",
|
|
169
|
+
speed: options?.speed,
|
|
170
|
+
});
|
|
171
|
+
// Markup whatever the console says, as Rich's `Text.from_markup`. A
|
|
172
|
+
// `RichText` is copied, since the caller still holds it.
|
|
173
|
+
const finishedText = options?.finishedText ?? " ";
|
|
174
|
+
this._finishedText = typeof finishedText === "string" ? readStr(finishedText, true) : finishedText.copy();
|
|
82
175
|
}
|
|
83
|
-
|
|
84
|
-
|
|
176
|
+
render(task) {
|
|
177
|
+
return finished(task) ? this._finishedText : this._spinner;
|
|
85
178
|
}
|
|
86
179
|
}
|
|
180
|
+
/**
|
|
181
|
+
* Rich's `MofNCompleteColumn`: both counts as whole numbers, the completed one
|
|
182
|
+
* padded to the total's width so the column holds still as it counts up to its
|
|
183
|
+
* total, styled `progress.download`.
|
|
184
|
+
*/
|
|
87
185
|
export class MofNCompleteColumn {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
186
|
+
tableColumn = {};
|
|
187
|
+
separator;
|
|
188
|
+
constructor(options) {
|
|
189
|
+
this.separator = options?.separator ?? "/";
|
|
190
|
+
}
|
|
191
|
+
render(task) {
|
|
192
|
+
const total = task.total === undefined ? "?" : wholeNumber(task.total);
|
|
193
|
+
const completed = wholeNumber(task.completed).padStart(total.length);
|
|
194
|
+
return new RichText(`${completed}${this.separator}${total}`, { style: "progress.download" });
|
|
92
195
|
}
|
|
93
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* Python's `str(int(x))`: truncated toward zero and written out in full,
|
|
199
|
+
* never in exponent form. `Progress` admits only finite counts, so `BigInt`
|
|
200
|
+
* has nothing here to refuse.
|
|
201
|
+
*/
|
|
202
|
+
function wholeNumber(x) {
|
|
203
|
+
return BigInt(Math.trunc(x)).toString();
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Rich's clock face: `int()` the seconds, then Python's floor `divmod` by 60
|
|
207
|
+
* twice, so a negative estimate — a task past its total — reads as Rich's
|
|
208
|
+
* does, `-3` as `-1:59:57`.
|
|
209
|
+
*/
|
|
94
210
|
function formatTime(seconds) {
|
|
95
|
-
const
|
|
96
|
-
const
|
|
97
|
-
const
|
|
211
|
+
const whole = Math.trunc(seconds);
|
|
212
|
+
const minutes = Math.floor(whole / 60);
|
|
213
|
+
const h = Math.floor(minutes / 60);
|
|
214
|
+
const m = minutes - h * 60;
|
|
215
|
+
const s = whole - minutes * 60;
|
|
98
216
|
return `${h}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
|
|
99
217
|
}
|
|
100
218
|
export class Progress {
|
|
@@ -103,6 +221,7 @@ export class Progress {
|
|
|
103
221
|
_nextId;
|
|
104
222
|
_live;
|
|
105
223
|
_console;
|
|
224
|
+
_clock;
|
|
106
225
|
expand;
|
|
107
226
|
constructor(...columns) {
|
|
108
227
|
// Last arg might be options
|
|
@@ -122,10 +241,12 @@ export class Progress {
|
|
|
122
241
|
this._columns = cols;
|
|
123
242
|
this._tasks = new Map();
|
|
124
243
|
this._nextId = 1;
|
|
125
|
-
this._console = opts.console ?? new Console(
|
|
244
|
+
this._console = opts.console ?? new Console();
|
|
126
245
|
this.expand = opts.expand ?? false;
|
|
246
|
+
this._clock = opts.clock ?? systemClock();
|
|
127
247
|
this._live = new Live(this, {
|
|
128
248
|
console: this._console,
|
|
249
|
+
clock: this._clock,
|
|
129
250
|
refreshPerSecond: opts.refreshPerSecond ?? 10,
|
|
130
251
|
autoRefresh: opts.autoRefresh,
|
|
131
252
|
transient: opts.transient,
|
|
@@ -134,13 +255,9 @@ export class Progress {
|
|
|
134
255
|
get console() {
|
|
135
256
|
return this._console;
|
|
136
257
|
}
|
|
258
|
+
/** Rich's `Progress.finished`: every task finished, and so true with none. */
|
|
137
259
|
get finished() {
|
|
138
|
-
|
|
139
|
-
if (!task.started || (task.total !== undefined && task.completed < task.total)) {
|
|
140
|
-
return false;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
return this._tasks.size > 0;
|
|
260
|
+
return [...this._tasks.values()].every(finished);
|
|
144
261
|
}
|
|
145
262
|
static getDefaultColumns() {
|
|
146
263
|
return [
|
|
@@ -155,12 +272,12 @@ export class Progress {
|
|
|
155
272
|
const task = {
|
|
156
273
|
id,
|
|
157
274
|
description,
|
|
158
|
-
total: options?.total,
|
|
275
|
+
total: options?.total === undefined ? undefined : finiteCount("total", options.total),
|
|
159
276
|
completed: 0,
|
|
160
|
-
started: options?.start !== false,
|
|
161
277
|
visible: options?.visible !== false,
|
|
162
|
-
startTime:
|
|
163
|
-
|
|
278
|
+
startTime: options?.start === false ? undefined : this._clock.now(),
|
|
279
|
+
finishedTime: undefined,
|
|
280
|
+
samples: [],
|
|
164
281
|
};
|
|
165
282
|
this._tasks.set(id, task);
|
|
166
283
|
return id;
|
|
@@ -169,24 +286,35 @@ export class Progress {
|
|
|
169
286
|
const task = this._tasks.get(taskId);
|
|
170
287
|
if (!task)
|
|
171
288
|
return;
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
289
|
+
// Rich's order: `advance` is added, then a given `completed` overwrites it,
|
|
290
|
+
// so `completed` wins when both come. Checked whole before any field is
|
|
291
|
+
// written, so a refused update leaves the task as it was.
|
|
292
|
+
const advance = finiteCount("advance", options.advance ?? 0);
|
|
293
|
+
const completed = options.completed === undefined
|
|
294
|
+
? finiteCount("completed + advance", task.completed + advance)
|
|
295
|
+
: finiteCount("completed", options.completed);
|
|
296
|
+
const now = this._clock.now();
|
|
297
|
+
recordSample(task.samples, now, completed - task.completed);
|
|
298
|
+
task.completed = completed;
|
|
176
299
|
if (options.description !== undefined)
|
|
177
300
|
task.description = options.description;
|
|
178
301
|
if (options.visible !== undefined)
|
|
179
302
|
task.visible = options.visible;
|
|
303
|
+
if (!finished(task) &&
|
|
304
|
+
task.startTime !== undefined &&
|
|
305
|
+
task.total !== undefined &&
|
|
306
|
+
task.completed >= task.total) {
|
|
307
|
+
task.finishedTime = now - task.startTime;
|
|
308
|
+
}
|
|
180
309
|
if (options.refresh) {
|
|
181
310
|
this._live.update(this, { refresh: true });
|
|
182
311
|
}
|
|
183
312
|
}
|
|
184
313
|
startTask(taskId) {
|
|
185
314
|
const task = this._tasks.get(taskId);
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
task.startTime
|
|
189
|
-
}
|
|
315
|
+
// Rich's `start_task`: a task already running keeps the time it started.
|
|
316
|
+
if (task)
|
|
317
|
+
task.startTime ??= this._clock.now();
|
|
190
318
|
}
|
|
191
319
|
start() {
|
|
192
320
|
this._live.start();
|
|
@@ -198,23 +326,26 @@ export class Progress {
|
|
|
198
326
|
this._live.refresh();
|
|
199
327
|
}
|
|
200
328
|
*render(options) {
|
|
329
|
+
// Rich's `make_tasks_table`: each column laid out as it says.
|
|
201
330
|
const table = Table.grid({ expand: this.expand });
|
|
202
|
-
for (const
|
|
203
|
-
table.addColumn();
|
|
331
|
+
for (const col of this._columns) {
|
|
332
|
+
table.addColumn(undefined, col.tableColumn);
|
|
204
333
|
}
|
|
205
|
-
|
|
206
|
-
|
|
334
|
+
// [LAW:effects-at-boundaries] One read of the clock a frame, handed to
|
|
335
|
+
// every column as the task's `time` and `elapsed`.
|
|
336
|
+
const now = this._clock.now();
|
|
337
|
+
for (const { samples, ...state } of this._tasks.values()) {
|
|
338
|
+
if (!state.visible)
|
|
207
339
|
continue;
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
table.addRow(...cells);
|
|
340
|
+
const { startTime } = state;
|
|
341
|
+
const started = startTime !== undefined;
|
|
342
|
+
const task = {
|
|
343
|
+
...state,
|
|
344
|
+
time: now,
|
|
345
|
+
elapsed: started ? now - startTime : undefined,
|
|
346
|
+
speed: started ? sampleSpeed(samples) : undefined,
|
|
347
|
+
};
|
|
348
|
+
table.addRow(...this._columns.map((col) => col.render(task)));
|
|
218
349
|
}
|
|
219
350
|
yield* table.render(options);
|
|
220
351
|
}
|
|
@@ -8,7 +8,15 @@ export interface ProgressBarOptions {
|
|
|
8
8
|
total?: number;
|
|
9
9
|
completed?: number;
|
|
10
10
|
width?: number;
|
|
11
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Rich's pulse, for work whose end is not known, drawn as it is `t` seconds
|
|
13
|
+
* into whoever draws the frames' clock: the whole bar in its back style,
|
|
14
|
+
* with the library's `shimmer` carrying `bar.pulse` light along it. Without
|
|
15
|
+
* it, the bar shows `completed` of `total`.
|
|
16
|
+
*/
|
|
17
|
+
pulse?: {
|
|
18
|
+
readonly t: number;
|
|
19
|
+
};
|
|
12
20
|
style?: string | Style;
|
|
13
21
|
completeStyle?: string | Style;
|
|
14
22
|
finishedStyle?: string | Style;
|
|
@@ -17,14 +25,26 @@ export declare class ProgressBar implements Renderable, Measurable {
|
|
|
17
25
|
total: number;
|
|
18
26
|
completed: number;
|
|
19
27
|
readonly width: number | undefined;
|
|
20
|
-
readonly pulse:
|
|
28
|
+
readonly pulse: {
|
|
29
|
+
readonly t: number;
|
|
30
|
+
} | undefined;
|
|
21
31
|
readonly style: string | Style;
|
|
22
32
|
readonly completeStyle: string | Style;
|
|
23
33
|
readonly finishedStyle: string | Style;
|
|
24
34
|
constructor(options?: ProgressBarOptions);
|
|
25
|
-
get percentComplete(): number;
|
|
26
35
|
render(options: RenderOptions): Iterable<Segment>;
|
|
27
|
-
|
|
36
|
+
/**
|
|
37
|
+
* The shimmer over this bar's back colour as `Effected` sees it drawn at
|
|
38
|
+
* `depth`, toward `bar.pulse`. A bar has no words to keep legible, so its
|
|
39
|
+
* back takes the whole of the light.
|
|
40
|
+
*/
|
|
41
|
+
private pulseEffect;
|
|
42
|
+
/** Rich 9d8f9a3 `__rich_console__`: the fill is counted in half cells, and a total of zero is a bar already full. */
|
|
43
|
+
private completeHalves;
|
|
44
|
+
/** The bar `width` cells wide with `completeHalves` half cells filled, the rest in `back`. */
|
|
45
|
+
private track;
|
|
46
|
+
/** A width given is the bar's exact width, as Rich measures it; one left open fills what it is offered, down to 4. */
|
|
47
|
+
measure(options: RenderOptions): {
|
|
28
48
|
minimum: number;
|
|
29
49
|
maximum: number;
|
|
30
50
|
};
|