effective-progress 0.2.4 → 0.3.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 +51 -32
- package/package.json +1 -1
- package/src/api.ts +20 -7
- package/src/colors.ts +19 -145
- package/src/renderer.ts +166 -181
- package/src/runtime.ts +41 -26
- package/src/terminal.ts +12 -10
- package/src/types.ts +7 -9
package/README.md
CHANGED
|
@@ -105,10 +105,6 @@ const configured = program.pipe(
|
|
|
105
105
|
}),
|
|
106
106
|
Effect.provideService(Progress.ProgressBarConfig, {
|
|
107
107
|
barWidth: 36,
|
|
108
|
-
colors: {
|
|
109
|
-
fill: { kind: "hex", value: "#00b894" },
|
|
110
|
-
spinner: { kind: "ansi256", value: 214 },
|
|
111
|
-
},
|
|
112
108
|
}),
|
|
113
109
|
);
|
|
114
110
|
|
|
@@ -124,9 +120,6 @@ yield *
|
|
|
124
120
|
progressbar: {
|
|
125
121
|
barWidth: 20,
|
|
126
122
|
spinnerFrames: [".", "o", "O", "0"],
|
|
127
|
-
colors: {
|
|
128
|
-
spinner: { kind: "named", value: "magentaBright" },
|
|
129
|
-
},
|
|
130
123
|
},
|
|
131
124
|
});
|
|
132
125
|
```
|
|
@@ -177,38 +170,64 @@ const program = Progress.task(
|
|
|
177
170
|
|
|
178
171
|
## Progress bar colors
|
|
179
172
|
|
|
180
|
-
`
|
|
173
|
+
Colors are configured through the `Colorizer` service. You can set a global colorizer, or provide per-task overrides using `Effect.provideService`.
|
|
174
|
+
|
|
175
|
+
### Global colorizer
|
|
176
|
+
|
|
177
|
+
Provide a custom `Colorizer` at the top level to change colors for all tasks:
|
|
181
178
|
|
|
182
179
|
```ts
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
180
|
+
import chalk from "chalk";
|
|
181
|
+
import { Effect } from "effect";
|
|
182
|
+
import * as Progress from "effective-progress";
|
|
183
|
+
|
|
184
|
+
const program = Progress.task(myEffect, { description: "Work" }).pipe(
|
|
185
|
+
Effect.provideService(
|
|
186
|
+
Progress.Colorizer,
|
|
187
|
+
Progress.Colorizer.of({
|
|
188
|
+
fill: chalk.hex("#00b894"),
|
|
189
|
+
empty: chalk.white.dim,
|
|
190
|
+
brackets: chalk.rgb(180, 190, 210),
|
|
191
|
+
percent: chalk.whiteBright.bold,
|
|
192
|
+
spinner: chalk.ansi256(214),
|
|
193
|
+
done: chalk.greenBright,
|
|
194
|
+
failed: chalk.redBright.bold,
|
|
195
|
+
}),
|
|
196
|
+
),
|
|
197
|
+
);
|
|
200
198
|
```
|
|
201
199
|
|
|
202
|
-
|
|
200
|
+
### Per-task colorizer
|
|
201
|
+
|
|
202
|
+
Wrap any effect with `Effect.provideService(Colorizer, ...)` to override colors for that task (and its children). The colorizer is captured at task-creation time, so each task can have its own colors:
|
|
203
203
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
204
|
+
```ts
|
|
205
|
+
Progress.forEach(
|
|
206
|
+
["fetch", "transform", "persist"],
|
|
207
|
+
(stage) => Effect.gen(function* () {
|
|
208
|
+
yield* Effect.sleep("500 millis");
|
|
209
|
+
return stage;
|
|
210
|
+
}),
|
|
211
|
+
{ description: "Worker pipeline" },
|
|
212
|
+
).pipe(
|
|
213
|
+
Effect.provideService(
|
|
214
|
+
Progress.Colorizer,
|
|
215
|
+
Progress.Colorizer.of({
|
|
216
|
+
fill: chalk.red,
|
|
217
|
+
empty: chalk.white.dim,
|
|
218
|
+
brackets: chalk.white.dim,
|
|
219
|
+
percent: chalk.white.bold,
|
|
220
|
+
spinner: chalk.magentaBright,
|
|
221
|
+
done: chalk.greenBright,
|
|
222
|
+
failed: chalk.redBright,
|
|
223
|
+
}),
|
|
224
|
+
),
|
|
225
|
+
);
|
|
226
|
+
```
|
|
208
227
|
|
|
209
|
-
|
|
228
|
+
Tasks inherit the colorizer from their parent task.
|
|
210
229
|
|
|
211
|
-
|
|
230
|
+
Each `ColorizerService` slot (`fill`, `empty`, `brackets`, `percent`, `spinner`, `done`, `failed`) is a `(text: string) => string` function. Use any chalk style — named colors, hex, rgb, ansi256, and modifiers like `.bold` or `.dim` all work.
|
|
212
231
|
|
|
213
232
|
## Dependencies & package size
|
|
214
233
|
|
package/package.json
CHANGED
package/src/api.ts
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
|
-
import { Effect, Exit } from "effect";
|
|
1
|
+
import { Effect, Exit, Option } from "effect";
|
|
2
2
|
import { dual } from "effect/Function";
|
|
3
3
|
import type { Concurrency } from "effect/Types";
|
|
4
|
-
import { Progress
|
|
4
|
+
import { Progress } from "./runtime";
|
|
5
5
|
import { Task } from "./types";
|
|
6
6
|
import type { AddTaskOptions, TrackOptions } from "./types";
|
|
7
7
|
import { inferTotal } from "./utils";
|
|
8
8
|
|
|
9
|
+
const provideProgress = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
|
|
10
|
+
Effect.gen(function* () {
|
|
11
|
+
const existing = yield* Effect.serviceOption(Progress);
|
|
12
|
+
if (Option.isSome(existing)) {
|
|
13
|
+
return yield* Effect.provideService(effect, Progress, existing.value);
|
|
14
|
+
}
|
|
15
|
+
return yield* Effect.scoped(effect.pipe(Effect.provide(Progress.Default)));
|
|
16
|
+
});
|
|
17
|
+
|
|
9
18
|
export interface EffectExecutionOptions {
|
|
10
19
|
readonly concurrency?: Concurrency;
|
|
11
20
|
readonly batching?: boolean | "inherit";
|
|
@@ -19,7 +28,9 @@ export interface EffectAllExecutionOptions extends EffectExecutionOptions {
|
|
|
19
28
|
|
|
20
29
|
export type AllOptions = Omit<TrackOptions, "total"> & EffectAllExecutionOptions;
|
|
21
30
|
export type AllReturn<
|
|
22
|
-
Arg extends
|
|
31
|
+
Arg extends
|
|
32
|
+
| ReadonlyArray<Effect.Effect<any, any, any>>
|
|
33
|
+
| Record<string, Effect.Effect<any, any, any>>,
|
|
23
34
|
O extends EffectAllExecutionOptions,
|
|
24
35
|
> = [
|
|
25
36
|
[Arg] extends [ReadonlyArray<Effect.Effect<any, any, any>>]
|
|
@@ -48,7 +59,7 @@ export const task: {
|
|
|
48
59
|
} = dual(
|
|
49
60
|
2,
|
|
50
61
|
<A, E, R>(effect: Effect.Effect<A, E, R>, options: AddTaskOptions) =>
|
|
51
|
-
|
|
62
|
+
provideProgress(
|
|
52
63
|
Effect.gen(function* () {
|
|
53
64
|
const progress = yield* Progress;
|
|
54
65
|
return yield* progress.withTask(effect, options);
|
|
@@ -56,7 +67,9 @@ export const task: {
|
|
|
56
67
|
) as Effect.Effect<A, E, Exclude<R, Progress | Task>>,
|
|
57
68
|
);
|
|
58
69
|
|
|
59
|
-
type AllArg =
|
|
70
|
+
type AllArg =
|
|
71
|
+
| ReadonlyArray<Effect.Effect<any, any, any>>
|
|
72
|
+
| Record<string, Effect.Effect<any, any, any>>;
|
|
60
73
|
|
|
61
74
|
const wrapEffects = (
|
|
62
75
|
effects: AllArg,
|
|
@@ -83,7 +96,7 @@ export const all: {
|
|
|
83
96
|
effects: Arg,
|
|
84
97
|
options: Omit<TrackOptions, "total"> & O,
|
|
85
98
|
) =>
|
|
86
|
-
|
|
99
|
+
provideProgress(
|
|
87
100
|
Effect.gen(function* () {
|
|
88
101
|
const progress = yield* Progress;
|
|
89
102
|
return yield* progress.runTask(
|
|
@@ -143,7 +156,7 @@ export const forEach: {
|
|
|
143
156
|
f: (item: A, index: number) => Effect.Effect<B, E, R>,
|
|
144
157
|
options: ForEachOptions,
|
|
145
158
|
) =>
|
|
146
|
-
|
|
159
|
+
provideProgress(
|
|
147
160
|
Effect.gen(function* () {
|
|
148
161
|
const progress = yield* Progress;
|
|
149
162
|
|
package/src/colors.ts
CHANGED
|
@@ -1,141 +1,7 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
-
import
|
|
3
|
-
import { Schema } from "effect";
|
|
2
|
+
import { Context, Layer } from "effect";
|
|
4
3
|
|
|
5
|
-
export
|
|
6
|
-
"bold",
|
|
7
|
-
"dim",
|
|
8
|
-
"italic",
|
|
9
|
-
"underline",
|
|
10
|
-
"inverse",
|
|
11
|
-
"hidden",
|
|
12
|
-
"strikethrough",
|
|
13
|
-
);
|
|
14
|
-
export type StyleModifier = typeof StyleModifierSchema.Type;
|
|
15
|
-
|
|
16
|
-
export const NamedColorSchema = Schema.Literal(
|
|
17
|
-
"black",
|
|
18
|
-
"red",
|
|
19
|
-
"green",
|
|
20
|
-
"yellow",
|
|
21
|
-
"blue",
|
|
22
|
-
"magenta",
|
|
23
|
-
"cyan",
|
|
24
|
-
"white",
|
|
25
|
-
"blackBright",
|
|
26
|
-
"redBright",
|
|
27
|
-
"greenBright",
|
|
28
|
-
"yellowBright",
|
|
29
|
-
"blueBright",
|
|
30
|
-
"magentaBright",
|
|
31
|
-
"cyanBright",
|
|
32
|
-
"whiteBright",
|
|
33
|
-
);
|
|
34
|
-
export type NamedColor = typeof NamedColorSchema.Type;
|
|
35
|
-
|
|
36
|
-
const HexColorSchema = Schema.String.pipe(Schema.pattern(/^#(?:[A-Fa-f0-9]{3}|[A-Fa-f0-9]{6})$/));
|
|
37
|
-
const ColorChannelSchema = Schema.Number.pipe(
|
|
38
|
-
Schema.int(),
|
|
39
|
-
Schema.greaterThanOrEqualTo(0),
|
|
40
|
-
Schema.lessThanOrEqualTo(255),
|
|
41
|
-
);
|
|
42
|
-
const Ansi256Schema = Schema.Number.pipe(
|
|
43
|
-
Schema.int(),
|
|
44
|
-
Schema.greaterThanOrEqualTo(0),
|
|
45
|
-
Schema.lessThanOrEqualTo(255),
|
|
46
|
-
);
|
|
47
|
-
const ModifiersSchema = Schema.optional(Schema.Array(StyleModifierSchema));
|
|
48
|
-
|
|
49
|
-
export const NamedColorStyleSchema = Schema.Struct({
|
|
50
|
-
kind: Schema.Literal("named"),
|
|
51
|
-
value: NamedColorSchema,
|
|
52
|
-
modifiers: ModifiersSchema,
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
export const HexColorStyleSchema = Schema.Struct({
|
|
56
|
-
kind: Schema.Literal("hex"),
|
|
57
|
-
value: HexColorSchema,
|
|
58
|
-
modifiers: ModifiersSchema,
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
export const RgbColorStyleSchema = Schema.Struct({
|
|
62
|
-
kind: Schema.Literal("rgb"),
|
|
63
|
-
value: Schema.Struct({
|
|
64
|
-
r: ColorChannelSchema,
|
|
65
|
-
g: ColorChannelSchema,
|
|
66
|
-
b: ColorChannelSchema,
|
|
67
|
-
}),
|
|
68
|
-
modifiers: ModifiersSchema,
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
export const Ansi256ColorStyleSchema = Schema.Struct({
|
|
72
|
-
kind: Schema.Literal("ansi256"),
|
|
73
|
-
value: Ansi256Schema,
|
|
74
|
-
modifiers: ModifiersSchema,
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
export const ColorStyleSchema = Schema.Union(
|
|
78
|
-
NamedColorStyleSchema,
|
|
79
|
-
HexColorStyleSchema,
|
|
80
|
-
RgbColorStyleSchema,
|
|
81
|
-
Ansi256ColorStyleSchema,
|
|
82
|
-
);
|
|
83
|
-
export type ColorStyle = typeof ColorStyleSchema.Type;
|
|
84
|
-
|
|
85
|
-
export const ProgressBarColorsSchema = Schema.Struct({
|
|
86
|
-
fill: ColorStyleSchema,
|
|
87
|
-
empty: ColorStyleSchema,
|
|
88
|
-
brackets: ColorStyleSchema,
|
|
89
|
-
percent: ColorStyleSchema,
|
|
90
|
-
spinner: ColorStyleSchema,
|
|
91
|
-
done: ColorStyleSchema,
|
|
92
|
-
failed: ColorStyleSchema,
|
|
93
|
-
});
|
|
94
|
-
export type ProgressBarColors = typeof ProgressBarColorsSchema.Type;
|
|
95
|
-
|
|
96
|
-
export const defaultProgressBarColors: ProgressBarColors = {
|
|
97
|
-
fill: { kind: "named", value: "cyan" },
|
|
98
|
-
empty: { kind: "named", value: "white", modifiers: ["dim"] },
|
|
99
|
-
brackets: { kind: "named", value: "white", modifiers: ["dim"] },
|
|
100
|
-
percent: { kind: "named", value: "white", modifiers: ["bold"] },
|
|
101
|
-
spinner: { kind: "named", value: "yellow" },
|
|
102
|
-
done: { kind: "named", value: "green" },
|
|
103
|
-
failed: { kind: "named", value: "red" },
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
const applyModifier = (instance: ChalkInstance, modifier: StyleModifier): ChalkInstance =>
|
|
107
|
-
instance[modifier];
|
|
108
|
-
|
|
109
|
-
const applyNamedColor = (instance: ChalkInstance, color: NamedColor): ChalkInstance =>
|
|
110
|
-
instance[color];
|
|
111
|
-
|
|
112
|
-
const resolveBaseStyle = (style: ColorStyle): ChalkInstance => {
|
|
113
|
-
switch (style.kind) {
|
|
114
|
-
case "named":
|
|
115
|
-
return applyNamedColor(chalk, style.value);
|
|
116
|
-
case "hex":
|
|
117
|
-
return chalk.hex(style.value);
|
|
118
|
-
case "rgb":
|
|
119
|
-
return chalk.rgb(style.value.r, style.value.g, style.value.b);
|
|
120
|
-
case "ansi256":
|
|
121
|
-
return chalk.ansi256(style.value);
|
|
122
|
-
}
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
const applyModifiers = (instance: ChalkInstance, modifiers: ReadonlyArray<StyleModifier>) => {
|
|
126
|
-
let styled = instance;
|
|
127
|
-
for (const modifier of modifiers) {
|
|
128
|
-
styled = applyModifier(styled, modifier);
|
|
129
|
-
}
|
|
130
|
-
return styled;
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
export const applyColorStyle = (style: ColorStyle, text: string): string => {
|
|
134
|
-
const styled = applyModifiers(resolveBaseStyle(style), style.modifiers ?? []);
|
|
135
|
-
return styled(text);
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
export interface CompiledProgressBarColors {
|
|
4
|
+
export interface ColorizerService {
|
|
139
5
|
readonly fill: (text: string) => string;
|
|
140
6
|
readonly empty: (text: string) => string;
|
|
141
7
|
readonly brackets: (text: string) => string;
|
|
@@ -145,12 +11,20 @@ export interface CompiledProgressBarColors {
|
|
|
145
11
|
readonly failed: (text: string) => string;
|
|
146
12
|
}
|
|
147
13
|
|
|
148
|
-
export
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
14
|
+
export class Colorizer extends Context.Tag("stromseng.dev/effective-progress/Colorizer")<
|
|
15
|
+
Colorizer,
|
|
16
|
+
ColorizerService
|
|
17
|
+
>() {
|
|
18
|
+
static readonly Default = Layer.succeed(
|
|
19
|
+
Colorizer,
|
|
20
|
+
Colorizer.of({
|
|
21
|
+
fill: chalk.blue,
|
|
22
|
+
empty: chalk.white.dim,
|
|
23
|
+
brackets: chalk.white.dim,
|
|
24
|
+
percent: chalk.white.bold,
|
|
25
|
+
spinner: chalk.yellow,
|
|
26
|
+
done: chalk.green,
|
|
27
|
+
failed: chalk.red,
|
|
28
|
+
}),
|
|
29
|
+
);
|
|
30
|
+
}
|
package/src/renderer.ts
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
import { Clock, Duration, Effect, Ref
|
|
2
|
-
import {
|
|
3
|
-
type CompiledProgressBarColors,
|
|
4
|
-
compileProgressBarColors,
|
|
5
|
-
ProgressBarColorsSchema,
|
|
6
|
-
} from "./colors";
|
|
1
|
+
import { Clock, Duration, Effect, Ref } from "effect";
|
|
2
|
+
import { Colorizer, type ColorizerService } from "./colors";
|
|
7
3
|
import type { ProgressTerminalService } from "./terminal";
|
|
8
4
|
import type { ProgressBarConfigShape, RendererConfigShape, TaskStore } from "./types";
|
|
9
5
|
import { DeterminateTaskUnits, TaskSnapshot } from "./types";
|
|
@@ -12,12 +8,11 @@ const HIDE_CURSOR = "\x1b[?25l";
|
|
|
12
8
|
const SHOW_CURSOR = "\x1b[?25h";
|
|
13
9
|
const CLEAR_LINE = "\x1b[2K";
|
|
14
10
|
const MOVE_UP_ONE = "\x1b[1A";
|
|
15
|
-
const encodeProgressBarColorsKey = Schema.encodeSync(Schema.parseJson(ProgressBarColorsSchema));
|
|
16
11
|
|
|
17
12
|
const renderDeterminate = (
|
|
18
13
|
units: DeterminateTaskUnits,
|
|
19
14
|
progressbar: ProgressBarConfigShape,
|
|
20
|
-
colors:
|
|
15
|
+
colors: ColorizerService,
|
|
21
16
|
): string => {
|
|
22
17
|
const safeTotal = units.total <= 0 ? 1 : units.total;
|
|
23
18
|
const ratio = Math.min(1, Math.max(0, units.completed / safeTotal));
|
|
@@ -40,7 +35,7 @@ const buildTaskLine = (
|
|
|
40
35
|
snapshot: TaskSnapshot,
|
|
41
36
|
depth: number,
|
|
42
37
|
tick: number,
|
|
43
|
-
colors:
|
|
38
|
+
colors: ColorizerService,
|
|
44
39
|
now: number,
|
|
45
40
|
): string => {
|
|
46
41
|
const progressbar = snapshot.config;
|
|
@@ -77,210 +72,200 @@ export const runProgressServiceRenderer = (
|
|
|
77
72
|
isTTY: boolean,
|
|
78
73
|
rendererConfig: RendererConfigShape,
|
|
79
74
|
maxRetainedLogLines: number,
|
|
80
|
-
) =>
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
75
|
+
) =>
|
|
76
|
+
Effect.gen(function* () {
|
|
77
|
+
const colorizer = yield* Colorizer;
|
|
78
|
+
const retainLogHistory = maxRetainedLogLines > 0;
|
|
79
|
+
let previousLineCount = 0;
|
|
80
|
+
let nonTTYTaskSignatureById = new Map<number, string>();
|
|
81
|
+
let tick = 0;
|
|
82
|
+
let rendererActive = false;
|
|
83
|
+
let sessionActive = false;
|
|
84
|
+
|
|
85
|
+
const clipTTYFrameLines = (lines: ReadonlyArray<string>) =>
|
|
86
|
+
Effect.gen(function* () {
|
|
87
|
+
const terminalRows = yield* terminal.stderrRows;
|
|
88
|
+
if (terminalRows === undefined) {
|
|
89
|
+
return lines;
|
|
90
|
+
}
|
|
95
91
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
92
|
+
const visibleLineLimit = Math.max(1, terminalRows - 1);
|
|
93
|
+
if (lines.length <= visibleLineLimit) {
|
|
94
|
+
return lines;
|
|
95
|
+
}
|
|
100
96
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if (terminalRows === undefined) {
|
|
105
|
-
return lines;
|
|
106
|
-
}
|
|
97
|
+
if (visibleLineLimit === 1) {
|
|
98
|
+
return [`... ${lines.length} lines hidden`];
|
|
99
|
+
}
|
|
107
100
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
101
|
+
const hiddenLineCount = lines.length - visibleLineLimit + 1;
|
|
102
|
+
return [
|
|
103
|
+
`... ${hiddenLineCount} lines hidden (showing latest lines)`,
|
|
104
|
+
...lines.slice(lines.length - (visibleLineLimit - 1)),
|
|
105
|
+
];
|
|
106
|
+
});
|
|
112
107
|
|
|
113
|
-
|
|
114
|
-
|
|
108
|
+
const startTTYSession = Effect.gen(function* () {
|
|
109
|
+
if (!isTTY || sessionActive) {
|
|
110
|
+
return;
|
|
115
111
|
}
|
|
116
112
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
`... ${hiddenLineCount} lines hidden (showing latest lines)`,
|
|
120
|
-
...lines.slice(lines.length - (visibleLineLimit - 1)),
|
|
121
|
-
];
|
|
113
|
+
yield* terminal.writeStderr(HIDE_CURSOR);
|
|
114
|
+
sessionActive = true;
|
|
122
115
|
});
|
|
123
116
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
yield* terminal.writeStderr(HIDE_CURSOR);
|
|
130
|
-
sessionActive = true;
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
const stopTTYSession = Effect.gen(function* () {
|
|
134
|
-
if (!isTTY || !sessionActive) {
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
117
|
+
const stopTTYSession = Effect.gen(function* () {
|
|
118
|
+
if (!isTTY || !sessionActive) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
137
121
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
122
|
+
yield* terminal.writeStderr("\n" + SHOW_CURSOR);
|
|
123
|
+
previousLineCount = 0;
|
|
124
|
+
sessionActive = false;
|
|
125
|
+
});
|
|
142
126
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
127
|
+
const renderNonTTYTaskUpdates = (
|
|
128
|
+
ordered: ReadonlyArray<{ snapshot: TaskSnapshot; depth: number }>,
|
|
129
|
+
taskLines: ReadonlyArray<string>,
|
|
130
|
+
) => {
|
|
131
|
+
const nextTaskSignatureById = new Map<number, string>();
|
|
132
|
+
const changedTaskLines: Array<string> = [];
|
|
133
|
+
const nonTtyUpdateStep = Math.max(1, Math.floor(rendererConfig.nonTtyUpdateStep));
|
|
134
|
+
|
|
135
|
+
for (let i = 0; i < ordered.length; i++) {
|
|
136
|
+
const taskId = ordered[i]!.snapshot.id as number;
|
|
137
|
+
const snapshot = ordered[i]!.snapshot;
|
|
138
|
+
const line = taskLines[i]!;
|
|
139
|
+
const signature =
|
|
140
|
+
snapshot.units._tag === "DeterminateTaskUnits"
|
|
141
|
+
? `${snapshot.status}:${snapshot.description}:${Math.floor(snapshot.units.completed / nonTtyUpdateStep)}:${snapshot.units.total}`
|
|
142
|
+
: `${snapshot.status}:${snapshot.description}`;
|
|
143
|
+
|
|
144
|
+
nextTaskSignatureById.set(taskId, signature);
|
|
145
|
+
if (nonTTYTaskSignatureById.get(taskId) !== signature) {
|
|
146
|
+
changedTaskLines.push(line);
|
|
147
|
+
}
|
|
163
148
|
}
|
|
164
|
-
}
|
|
165
149
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
150
|
+
return Effect.gen(function* () {
|
|
151
|
+
if (changedTaskLines.length > 0) {
|
|
152
|
+
yield* terminal.writeStderr(changedTaskLines.join("\n") + "\n");
|
|
153
|
+
}
|
|
170
154
|
|
|
171
|
-
|
|
172
|
-
});
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
const renderFrame = (mode: "tick" | "final") =>
|
|
176
|
-
Effect.gen(function* () {
|
|
177
|
-
const drainedLogs = yield* Ref.getAndSet(pendingLogsRef, []);
|
|
178
|
-
const store = yield* Ref.get(storeRef);
|
|
179
|
-
const ordered = store.renderOrder.flatMap((row) => {
|
|
180
|
-
const snapshot = store.tasks.get(row.id);
|
|
181
|
-
if (!snapshot || (snapshot.transient && snapshot.status !== "running")) return [];
|
|
182
|
-
return [{ snapshot, depth: row.depth }];
|
|
183
|
-
});
|
|
184
|
-
const now = yield* Clock.currentTimeMillis;
|
|
185
|
-
const frameTick = mode === "final" ? tick + 1 : tick;
|
|
186
|
-
const taskLines = ordered.map(({ snapshot, depth }) => {
|
|
187
|
-
const lineTick = isTTY ? frameTick : 0;
|
|
188
|
-
return buildTaskLine(snapshot, depth, lineTick, getCompiledColors(snapshot.config), now);
|
|
155
|
+
nonTTYTaskSignatureById = nextTaskSignatureById;
|
|
189
156
|
});
|
|
157
|
+
};
|
|
190
158
|
|
|
191
|
-
|
|
192
|
-
|
|
159
|
+
const renderFrame = (mode: "tick" | "final") =>
|
|
160
|
+
Effect.gen(function* () {
|
|
161
|
+
const drainedLogs = yield* Ref.getAndSet(pendingLogsRef, []);
|
|
162
|
+
const store = yield* Ref.get(storeRef);
|
|
163
|
+
const ordered = store.renderOrder.flatMap((row) => {
|
|
164
|
+
const snapshot = store.tasks.get(row.id);
|
|
165
|
+
if (!snapshot || (snapshot.transient && snapshot.status !== "running")) return [];
|
|
166
|
+
return [{ snapshot, depth: row.depth }];
|
|
167
|
+
});
|
|
168
|
+
const now = yield* Clock.currentTimeMillis;
|
|
169
|
+
const frameTick = mode === "final" ? tick + 1 : tick;
|
|
170
|
+
const taskLines = ordered.map(({ snapshot, depth }) => {
|
|
171
|
+
const lineTick = isTTY ? frameTick : 0;
|
|
172
|
+
const taskColorizer = store.colorizers.get(snapshot.id) ?? colorizer;
|
|
173
|
+
return buildTaskLine(snapshot, depth, lineTick, taskColorizer, now);
|
|
174
|
+
});
|
|
193
175
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
176
|
+
if (isTTY) {
|
|
177
|
+
let frame = "";
|
|
178
|
+
|
|
179
|
+
// 1. Cursor reset — move up and clear previous frame lines
|
|
180
|
+
if (previousLineCount > 0) {
|
|
181
|
+
frame += "\r" + CLEAR_LINE;
|
|
182
|
+
for (let i = 1; i < previousLineCount; i++) {
|
|
183
|
+
frame += MOVE_UP_ONE + CLEAR_LINE;
|
|
184
|
+
}
|
|
199
185
|
}
|
|
200
|
-
}
|
|
201
186
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
187
|
+
if (retainLogHistory) {
|
|
188
|
+
const historyLogs = yield* Ref.get(logsRef);
|
|
189
|
+
const lines = yield* clipTTYFrameLines([...historyLogs, ...taskLines]);
|
|
190
|
+
if (lines.length > 0) {
|
|
191
|
+
frame += lines.join("\n");
|
|
192
|
+
}
|
|
193
|
+
previousLineCount = lines.length;
|
|
194
|
+
} else {
|
|
195
|
+
// 2. Logs (scroll above the task block)
|
|
196
|
+
if (drainedLogs.length > 0) {
|
|
197
|
+
frame += drainedLogs.join("\n") + "\n";
|
|
198
|
+
}
|
|
199
|
+
// 3. Task lines
|
|
200
|
+
if (taskLines.length > 0) {
|
|
201
|
+
frame += taskLines.join("\n");
|
|
202
|
+
}
|
|
203
|
+
previousLineCount = taskLines.length;
|
|
207
204
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
frame += drainedLogs.join("\n") + "\n";
|
|
213
|
-
}
|
|
214
|
-
// 3. Task lines
|
|
215
|
-
if (taskLines.length > 0) {
|
|
216
|
-
frame += taskLines.join("\n");
|
|
205
|
+
|
|
206
|
+
// 4. Single atomic write
|
|
207
|
+
if (frame) {
|
|
208
|
+
yield* terminal.writeStderr(frame);
|
|
217
209
|
}
|
|
218
|
-
|
|
210
|
+
return;
|
|
219
211
|
}
|
|
220
212
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
yield* terminal.writeStderr(frame);
|
|
213
|
+
if (drainedLogs.length > 0) {
|
|
214
|
+
yield* terminal.writeStderr(drainedLogs.join("\n") + "\n");
|
|
224
215
|
}
|
|
225
|
-
|
|
226
|
-
}
|
|
216
|
+
yield* renderNonTTYTaskUpdates(ordered, taskLines);
|
|
217
|
+
});
|
|
227
218
|
|
|
228
|
-
|
|
229
|
-
|
|
219
|
+
const renderLoop = Effect.gen(function* () {
|
|
220
|
+
rendererActive = true;
|
|
221
|
+
if (isTTY) {
|
|
222
|
+
yield* startTTYSession;
|
|
230
223
|
}
|
|
231
|
-
yield* renderNonTTYTaskUpdates(ordered, taskLines);
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
const renderLoop = Effect.gen(function* () {
|
|
235
|
-
rendererActive = true;
|
|
236
|
-
if (isTTY) {
|
|
237
|
-
yield* startTTYSession;
|
|
238
|
-
}
|
|
239
224
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
225
|
+
while (true) {
|
|
226
|
+
const dirty = yield* Ref.getAndSet(dirtyRef, false);
|
|
227
|
+
const tasks = Array.from((yield* Ref.get(storeRef)).tasks.values()).filter(
|
|
228
|
+
(task) => !(task.transient && task.status !== "running"),
|
|
229
|
+
);
|
|
230
|
+
const hasActiveSpinners = tasks.some(
|
|
231
|
+
(task) => task.status === "running" && task.units._tag === "IndeterminateTaskUnits",
|
|
232
|
+
);
|
|
233
|
+
const hasPendingLogs = (yield* Ref.get(pendingLogsRef)).length > 0;
|
|
249
234
|
|
|
250
|
-
|
|
251
|
-
|
|
235
|
+
if (isTTY) {
|
|
236
|
+
if (dirty || hasActiveSpinners || hasPendingLogs) {
|
|
237
|
+
yield* renderFrame("tick");
|
|
238
|
+
}
|
|
239
|
+
} else if (dirty || hasActiveSpinners) {
|
|
252
240
|
yield* renderFrame("tick");
|
|
253
241
|
}
|
|
254
|
-
} else if (dirty || hasActiveSpinners) {
|
|
255
|
-
yield* renderFrame("tick");
|
|
256
|
-
}
|
|
257
242
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
243
|
+
tick += 1;
|
|
244
|
+
yield* Effect.sleep(Math.max(1, Math.floor(rendererConfig.renderIntervalMillis)));
|
|
245
|
+
}
|
|
246
|
+
}).pipe(
|
|
247
|
+
Effect.ensuring(
|
|
248
|
+
Effect.gen(function* () {
|
|
249
|
+
if (!rendererActive) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
267
252
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
253
|
+
if (isTTY) {
|
|
254
|
+
if (sessionActive) {
|
|
255
|
+
yield* renderFrame("final");
|
|
256
|
+
yield* stopTTYSession;
|
|
257
|
+
}
|
|
258
|
+
return;
|
|
272
259
|
}
|
|
273
|
-
return;
|
|
274
|
-
}
|
|
275
260
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
261
|
+
yield* renderFrame("final");
|
|
262
|
+
}),
|
|
263
|
+
),
|
|
264
|
+
);
|
|
280
265
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
266
|
+
if (isTTY && rendererConfig.disableUserInput) {
|
|
267
|
+
return yield* terminal.withRawInputCapture(renderLoop);
|
|
268
|
+
}
|
|
284
269
|
|
|
285
|
-
|
|
286
|
-
};
|
|
270
|
+
return yield* renderLoop;
|
|
271
|
+
});
|
package/src/runtime.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { dual } from "effect/Function";
|
|
|
3
3
|
import { mergeWith } from "es-toolkit/object";
|
|
4
4
|
import { formatWithOptions } from "node:util";
|
|
5
5
|
import type { PartialDeep } from "type-fest";
|
|
6
|
+
import { Colorizer, type ColorizerService } from "./colors";
|
|
6
7
|
import { makeProgressConsole } from "./console";
|
|
7
8
|
import { runProgressServiceRenderer } from "./renderer";
|
|
8
9
|
import { ProgressTerminal } from "./terminal";
|
|
@@ -119,7 +120,6 @@ const removeFromRenderOrder = (
|
|
|
119
120
|
const makeProgressService = Effect.gen(function* () {
|
|
120
121
|
const rendererConfigOption = yield* Effect.serviceOption(RendererConfig);
|
|
121
122
|
const progressBarConfigOption = yield* Effect.serviceOption(ProgressBarConfig);
|
|
122
|
-
|
|
123
123
|
const rendererConfig = decodeRendererConfigSync(
|
|
124
124
|
mergeConfig(
|
|
125
125
|
defaultRendererConfig,
|
|
@@ -140,6 +140,7 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
140
140
|
const storeRef = yield* Ref.make<TaskStore>({
|
|
141
141
|
tasks: new Map<TaskId, TaskSnapshot>(),
|
|
142
142
|
renderOrder: [],
|
|
143
|
+
colorizers: new Map<TaskId, ColorizerService>(),
|
|
143
144
|
});
|
|
144
145
|
const logsRef = yield* Ref.make<ReadonlyArray<string>>([]);
|
|
145
146
|
const pendingLogsRef = yield* Ref.make<ReadonlyArray<string>>([]);
|
|
@@ -169,6 +170,7 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
169
170
|
options.parentId === undefined
|
|
170
171
|
? yield* FiberRef.get(currentParentRef)
|
|
171
172
|
: Option.some(options.parentId);
|
|
173
|
+
const colorizerOption = yield* Effect.serviceOption(Colorizer);
|
|
172
174
|
const taskId = TaskId(yield* Ref.updateAndGet(nextTaskIdRef, (id) => id + 1));
|
|
173
175
|
const units =
|
|
174
176
|
options.total === undefined || options.total <= 0
|
|
@@ -203,7 +205,11 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
203
205
|
const { index, depth } = findInsertionIndex(s.renderOrder, parentIdValue);
|
|
204
206
|
const nextOrder = [...s.renderOrder];
|
|
205
207
|
nextOrder.splice(index, 0, { id: taskId, depth });
|
|
206
|
-
|
|
208
|
+
const nextColorizers = new Map(s.colorizers);
|
|
209
|
+
if (Option.isSome(colorizerOption)) {
|
|
210
|
+
nextColorizers.set(taskId, colorizerOption.value);
|
|
211
|
+
}
|
|
212
|
+
return { tasks: nextTasks, renderOrder: nextOrder, colorizers: nextColorizers };
|
|
207
213
|
});
|
|
208
214
|
yield* markDirty;
|
|
209
215
|
|
|
@@ -216,7 +222,7 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
216
222
|
if (!snapshot) return store;
|
|
217
223
|
const nextTasks = new Map(store.tasks);
|
|
218
224
|
nextTasks.set(taskId, updatedSnapshot(snapshot, options));
|
|
219
|
-
return { tasks: nextTasks, renderOrder: store.renderOrder };
|
|
225
|
+
return { tasks: nextTasks, renderOrder: store.renderOrder, colorizers: store.colorizers };
|
|
220
226
|
}).pipe(Effect.zipRight(markDirty));
|
|
221
227
|
|
|
222
228
|
const advanceTask = (taskId: TaskId, amount = 1) =>
|
|
@@ -250,7 +256,7 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
250
256
|
}),
|
|
251
257
|
);
|
|
252
258
|
|
|
253
|
-
return { tasks: nextTasks, renderOrder: store.renderOrder };
|
|
259
|
+
return { tasks: nextTasks, renderOrder: store.renderOrder, colorizers: store.colorizers };
|
|
254
260
|
}).pipe(Effect.zipRight(markDirty));
|
|
255
261
|
|
|
256
262
|
const completeTask = (taskId: TaskId) =>
|
|
@@ -263,7 +269,13 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
263
269
|
const nextTasks = new Map(store.tasks);
|
|
264
270
|
if (snapshot.transient) {
|
|
265
271
|
nextTasks.delete(taskId);
|
|
266
|
-
|
|
272
|
+
const nextColorizers = new Map(store.colorizers);
|
|
273
|
+
nextColorizers.delete(taskId);
|
|
274
|
+
return {
|
|
275
|
+
tasks: nextTasks,
|
|
276
|
+
renderOrder: removeFromRenderOrder(store.renderOrder, taskId),
|
|
277
|
+
colorizers: nextColorizers,
|
|
278
|
+
};
|
|
267
279
|
}
|
|
268
280
|
|
|
269
281
|
nextTasks.set(
|
|
@@ -286,7 +298,7 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
286
298
|
completedAt: now,
|
|
287
299
|
}),
|
|
288
300
|
);
|
|
289
|
-
return { tasks: nextTasks, renderOrder: store.renderOrder };
|
|
301
|
+
return { tasks: nextTasks, renderOrder: store.renderOrder, colorizers: store.colorizers };
|
|
290
302
|
});
|
|
291
303
|
yield* markDirty;
|
|
292
304
|
});
|
|
@@ -301,7 +313,13 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
301
313
|
const nextTasks = new Map(store.tasks);
|
|
302
314
|
if (snapshot.transient) {
|
|
303
315
|
nextTasks.delete(taskId);
|
|
304
|
-
|
|
316
|
+
const nextColorizers = new Map(store.colorizers);
|
|
317
|
+
nextColorizers.delete(taskId);
|
|
318
|
+
return {
|
|
319
|
+
tasks: nextTasks,
|
|
320
|
+
renderOrder: removeFromRenderOrder(store.renderOrder, taskId),
|
|
321
|
+
colorizers: nextColorizers,
|
|
322
|
+
};
|
|
305
323
|
}
|
|
306
324
|
|
|
307
325
|
nextTasks.set(
|
|
@@ -318,7 +336,7 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
318
336
|
completedAt: now,
|
|
319
337
|
}),
|
|
320
338
|
);
|
|
321
|
-
return { tasks: nextTasks, renderOrder: store.renderOrder };
|
|
339
|
+
return { tasks: nextTasks, renderOrder: store.renderOrder, colorizers: store.colorizers };
|
|
322
340
|
});
|
|
323
341
|
yield* markDirty;
|
|
324
342
|
});
|
|
@@ -424,22 +442,19 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
424
442
|
return Progress.of(service);
|
|
425
443
|
});
|
|
426
444
|
|
|
427
|
-
export class Progress extends Context.Tag("stromseng.dev/Progress")<
|
|
428
|
-
|
|
445
|
+
export class Progress extends Context.Tag("stromseng.dev/effective-progress/Progress")<
|
|
446
|
+
Progress,
|
|
447
|
+
ProgressService
|
|
448
|
+
>() {
|
|
449
|
+
static readonly Default = Layer.unwrapEffect(
|
|
450
|
+
Effect.gen(function* () {
|
|
451
|
+
const colorizerOption = yield* Effect.serviceOption(Colorizer);
|
|
452
|
+
const terminalOption = yield* Effect.serviceOption(ProgressTerminal);
|
|
453
|
+
const base = Layer.scoped(Progress, makeProgressService);
|
|
454
|
+
return base.pipe(
|
|
455
|
+
Option.isNone(colorizerOption) ? Layer.provide(Colorizer.Default) : (l) => l,
|
|
456
|
+
Option.isNone(terminalOption) ? Layer.provide(ProgressTerminal.Default) : (l) => l,
|
|
457
|
+
);
|
|
458
|
+
}),
|
|
459
|
+
);
|
|
429
460
|
}
|
|
430
|
-
|
|
431
|
-
export const provideProgressService = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
|
|
432
|
-
Effect.gen(function* () {
|
|
433
|
-
const existing = yield* Effect.serviceOption(Progress);
|
|
434
|
-
if (Option.isSome(existing)) {
|
|
435
|
-
return yield* Effect.provideService(effect, Progress, existing.value);
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
const existingTerminal = yield* Effect.serviceOption(ProgressTerminal);
|
|
439
|
-
if (Option.isSome(existingTerminal)) {
|
|
440
|
-
return yield* Effect.scoped(effect.pipe(Effect.provide(Progress.Default)));
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
const defaultLayers = Layer.provide(Progress.Default, ProgressTerminal.Default);
|
|
444
|
-
return yield* Effect.scoped(effect.pipe(Effect.provide(defaultLayers)));
|
|
445
|
-
});
|
package/src/terminal.ts
CHANGED
|
@@ -42,18 +42,20 @@ const withRawInputCapture: ProgressTerminalService["withRawInputCapture"] = (eff
|
|
|
42
42
|
);
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
+
const defaultTerminalService: ProgressTerminalService = {
|
|
46
|
+
isTTY: Effect.sync(() => Boolean(process.stderr.isTTY)),
|
|
47
|
+
stderrRows: Effect.sync(() => process.stderr.rows),
|
|
48
|
+
stderrColumns: Effect.sync(() => process.stderr.columns),
|
|
49
|
+
writeStderr: (text) =>
|
|
50
|
+
Effect.sync(() => {
|
|
51
|
+
process.stderr.write(text);
|
|
52
|
+
}),
|
|
53
|
+
withRawInputCapture,
|
|
54
|
+
};
|
|
55
|
+
|
|
45
56
|
export class ProgressTerminal extends Context.Tag("stromseng.dev/ProgressTerminal")<
|
|
46
57
|
ProgressTerminal,
|
|
47
58
|
ProgressTerminalService
|
|
48
59
|
>() {
|
|
49
|
-
static readonly Default = Layer.succeed(ProgressTerminal,
|
|
50
|
-
isTTY: Effect.sync(() => Boolean(process.stderr.isTTY)),
|
|
51
|
-
stderrRows: Effect.sync(() => process.stderr.rows),
|
|
52
|
-
stderrColumns: Effect.sync(() => process.stderr.columns),
|
|
53
|
-
writeStderr: (text) =>
|
|
54
|
-
Effect.sync(() => {
|
|
55
|
-
process.stderr.write(text);
|
|
56
|
-
}),
|
|
57
|
-
withRawInputCapture,
|
|
58
|
-
} satisfies ProgressTerminalService);
|
|
60
|
+
static readonly Default = Layer.succeed(ProgressTerminal, defaultTerminalService);
|
|
59
61
|
}
|
package/src/types.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Brand, Context, Effect, Option, Schema } from "effect";
|
|
2
2
|
import type { PartialDeep } from "type-fest";
|
|
3
|
-
import {
|
|
3
|
+
import type { ColorizerService } from "./colors";
|
|
4
4
|
|
|
5
5
|
export const RendererConfigSchema = Schema.Struct({
|
|
6
6
|
disableUserInput: Schema.Boolean,
|
|
@@ -18,7 +18,6 @@ export const ProgressBarConfigSchema = Schema.Struct({
|
|
|
18
18
|
emptyChar: Schema.String,
|
|
19
19
|
leftBracket: Schema.String,
|
|
20
20
|
rightBracket: Schema.String,
|
|
21
|
-
colors: ProgressBarColorsSchema,
|
|
22
21
|
});
|
|
23
22
|
export type ProgressBarConfigShape = typeof ProgressBarConfigSchema.Type;
|
|
24
23
|
export const decodeProgressBarConfigSync = Schema.decodeUnknownSync(ProgressBarConfigSchema);
|
|
@@ -37,18 +36,16 @@ export const defaultProgressBarConfig: ProgressBarConfigShape = {
|
|
|
37
36
|
emptyChar: "─",
|
|
38
37
|
leftBracket: "",
|
|
39
38
|
rightBracket: "",
|
|
40
|
-
colors: defaultProgressBarColors,
|
|
41
39
|
};
|
|
42
40
|
|
|
43
|
-
export class RendererConfig extends Context.Tag("stromseng.dev/RendererConfig")<
|
|
41
|
+
export class RendererConfig extends Context.Tag("stromseng.dev/effective-progress/RendererConfig")<
|
|
44
42
|
RendererConfig,
|
|
45
43
|
PartialDeep<RendererConfigShape>
|
|
46
44
|
>() {}
|
|
47
45
|
|
|
48
|
-
export class ProgressBarConfig extends Context.Tag(
|
|
49
|
-
ProgressBarConfig,
|
|
50
|
-
|
|
51
|
-
>() {}
|
|
46
|
+
export class ProgressBarConfig extends Context.Tag(
|
|
47
|
+
"stromseng.dev/effective-progress/ProgressBarConfig",
|
|
48
|
+
)<ProgressBarConfig, PartialDeep<ProgressBarConfigShape>>() {}
|
|
52
49
|
|
|
53
50
|
const TaskIdSchema = Schema.Number.pipe(Schema.brand("TaskId"));
|
|
54
51
|
|
|
@@ -115,6 +112,7 @@ export interface RenderRow {
|
|
|
115
112
|
export interface TaskStore {
|
|
116
113
|
readonly tasks: Map<TaskId, TaskSnapshot>;
|
|
117
114
|
readonly renderOrder: ReadonlyArray<RenderRow>;
|
|
115
|
+
readonly colorizers: Map<TaskId, ColorizerService>;
|
|
118
116
|
}
|
|
119
117
|
|
|
120
118
|
export interface ProgressService {
|
|
@@ -146,7 +144,7 @@ export interface ProgressService {
|
|
|
146
144
|
};
|
|
147
145
|
}
|
|
148
146
|
|
|
149
|
-
export class Task extends Context.Tag("stromseng.dev/Task")<Task, TaskId>() {}
|
|
147
|
+
export class Task extends Context.Tag("stromseng.dev/effective-progress/Task")<Task, TaskId>() {}
|
|
150
148
|
|
|
151
149
|
export class TaskAddedEvent extends Schema.TaggedClass<TaskAddedEvent>()("TaskAdded", {
|
|
152
150
|
taskId: TaskIdSchema,
|