@travetto/terminal 8.0.0-alpha.9 → 8.0.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 +8 -8
- package/__index__.ts +3 -2
- package/package.json +14 -14
- package/src/style.ts +35 -14
- package/src/terminal.ts +42 -19
- package/src/trv.d.ts +7 -7
- package/src/util.ts +49 -14
- package/src/writer.ts +14 -12
package/README.md
CHANGED
|
@@ -13,20 +13,20 @@ npm install @travetto/terminal
|
|
|
13
13
|
yarn add @travetto/terminal
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
This module provides basic support for interacting with the terminal, and provides the basis for output colorization and the basic command line interactions.
|
|
16
|
+
This module provides basic support for interacting with the terminal, and provides the basis for output colorization and the basic command line interactions. The functionality can be broken down into:
|
|
17
17
|
* Output Colorization
|
|
18
18
|
* Terminal Interactions
|
|
19
19
|
|
|
20
20
|
## Output Colorization
|
|
21
|
-
Oddly enough, colorizing output in a terminal is a fairly complex process.
|
|
21
|
+
Oddly enough, colorizing output in a terminal is a fairly complex process. The standards are somewhat inconsistent and detection can be a tricky process. For terminals, [Node](https://nodejs.org) supports 4 different levels of coloring:
|
|
22
22
|
* 0 - One color, essentially uncolored output
|
|
23
23
|
* 1 - Basic color support, 16 colors
|
|
24
24
|
* 2 - Enhanced color support, 225 colors, providing a fair representation of most colors
|
|
25
|
-
* 3 - True color, 24bit color with R, G, B each getting 8-bits.
|
|
25
|
+
* 3 - True color, 24bit color with R, G, B each getting 8-bits. Can represent any color needed
|
|
26
26
|
|
|
27
|
-
This module provides the ability to define color palettes using RGB colors, and additionally provides support for palettes based on a dark or light background for a given terminal.
|
|
27
|
+
This module provides the ability to define color palettes using RGB colors, and additionally provides support for palettes based on a dark or light background for a given terminal. Support for this is widespread, but when it fails, it will gracefully assume a dark background.
|
|
28
28
|
|
|
29
|
-
These palettes then are usable at runtime, with the module determining light or dark palettes, as well as falling back to the closest color value based on what the existing terminal supports.
|
|
29
|
+
These palettes then are usable at runtime, with the module determining light or dark palettes, as well as falling back to the closest color value based on what the existing terminal supports. This means a color like 'olivegreen', will get the proper output in 24bit color support, a close approximation in enhanced color support, fall back to green in basic color support, and will be color less at level 0.
|
|
30
30
|
|
|
31
31
|
**Code: CLI Color Palette**
|
|
32
32
|
```typescript
|
|
@@ -73,11 +73,11 @@ NODE_DISABLE_COLORS set to 1 to disable colors in the REPL
|
|
|
73
73
|
```
|
|
74
74
|
|
|
75
75
|
## Terminal Interactions
|
|
76
|
-
Within the [Travetto](https://travetto.dev) framework, there are plenty of command line interactions that are enhanced with additional interactivity.
|
|
76
|
+
Within the [Travetto](https://travetto.dev) framework, there are plenty of command line interactions that are enhanced with additional interactivity. This mainly revolves around indicating progress while a program is executing. The module provides support for:
|
|
77
77
|
* Progress Bars
|
|
78
78
|
* Waiting Indicators
|
|
79
79
|
* Streaming Content
|
|
80
80
|
|
|
81
|
-
This is generally meant for use within the framework, and so is highly tailored to the specific needs and scenarios.
|
|
81
|
+
This is generally meant for use within the framework, and so is highly tailored to the specific needs and scenarios. You can see this pattern play out in the [Compiler](https://github.com/travetto/travetto/tree/main/module/compiler#readme "The compiler infrastructure for the Travetto framework") progress output, or in [Pack](https://github.com/travetto/travetto/tree/main/module/pack#readme "Code packing utilities")'s output.
|
|
82
82
|
|
|
83
|
-
In these scenarios, the dynamic behaviors are dependent on having an interactive TTY.
|
|
83
|
+
In these scenarios, the dynamic behaviors are dependent on having an interactive TTY. When running without access to a proper stdin, the output will default to basic line printing. This dynamic behavior can also be disabled using the environment variable `TRV_QUIET`. When set to `1` will provide a minimal text-based experience.
|
package/__index__.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,38 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/terminal",
|
|
3
|
-
"version": "8.0.0
|
|
4
|
-
"
|
|
3
|
+
"version": "8.0.0",
|
|
4
|
+
"private": false,
|
|
5
5
|
"description": "General terminal support",
|
|
6
6
|
"keywords": [
|
|
7
|
-
"terminal",
|
|
8
7
|
"ansi256",
|
|
8
|
+
"terminal",
|
|
9
9
|
"travetto",
|
|
10
10
|
"typescript"
|
|
11
11
|
],
|
|
12
12
|
"homepage": "https://travetto.io",
|
|
13
13
|
"license": "MIT",
|
|
14
14
|
"author": {
|
|
15
|
-
"
|
|
16
|
-
"
|
|
15
|
+
"name": "Travetto Framework",
|
|
16
|
+
"email": "travetto.framework@gmail.com"
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"url": "git+https://github.com/travetto/travetto.git",
|
|
20
|
+
"directory": "module/terminal"
|
|
17
21
|
},
|
|
18
22
|
"files": [
|
|
19
23
|
"__index__.ts",
|
|
20
24
|
"src"
|
|
21
25
|
],
|
|
26
|
+
"type": "module",
|
|
22
27
|
"main": "__index__.ts",
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"directory": "module/terminal"
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
26
30
|
},
|
|
27
31
|
"dependencies": {
|
|
28
|
-
"@travetto/runtime": "^8.0.0
|
|
29
|
-
"chalk": "^
|
|
32
|
+
"@travetto/runtime": "^8.0.0",
|
|
33
|
+
"chalk": "^6.0.0"
|
|
30
34
|
},
|
|
31
35
|
"travetto": {
|
|
32
36
|
"displayName": "Terminal"
|
|
33
|
-
},
|
|
34
|
-
"private": false,
|
|
35
|
-
"publishConfig": {
|
|
36
|
-
"access": "public"
|
|
37
37
|
}
|
|
38
38
|
}
|
package/src/style.ts
CHANGED
|
@@ -4,17 +4,34 @@ import { Env, TypedObject } from '@travetto/runtime';
|
|
|
4
4
|
|
|
5
5
|
type TemplatePrim = number | string | bigint | boolean | RegExp;
|
|
6
6
|
type Color = `#${string}`;
|
|
7
|
-
export type TermStyleInput =
|
|
7
|
+
export type TermStyleInput =
|
|
8
|
+
| Color
|
|
9
|
+
| { text: Color; background?: Color; inverse?: boolean; bold?: boolean; italic?: boolean; underline?: boolean };
|
|
8
10
|
type TermStylePairInput = [dark: TermStyleInput, light?: TermStyleInput] | readonly [dark: TermStyleInput, light?: TermStyleInput];
|
|
9
11
|
export type TermStyleFn = (input: TemplatePrim) => string;
|
|
10
|
-
export type TermStyledTemplate<T extends string> = (
|
|
12
|
+
export type TermStyledTemplate<T extends string> = (
|
|
13
|
+
values: TemplateStringsArray,
|
|
14
|
+
...keys: (Partial<Record<T, TemplatePrim>> | string)[]
|
|
15
|
+
) => string;
|
|
11
16
|
export type ColorLevel = 0 | 1 | 2 | 3;
|
|
12
17
|
|
|
13
18
|
const ANSI_16_RGB: [number, number, number][] = [
|
|
14
|
-
[0, 0, 0],
|
|
15
|
-
[
|
|
16
|
-
[
|
|
17
|
-
[
|
|
19
|
+
[0, 0, 0],
|
|
20
|
+
[128, 0, 0],
|
|
21
|
+
[0, 128, 0],
|
|
22
|
+
[128, 128, 0],
|
|
23
|
+
[0, 0, 128],
|
|
24
|
+
[128, 0, 128],
|
|
25
|
+
[0, 128, 128],
|
|
26
|
+
[192, 192, 192],
|
|
27
|
+
[128, 128, 128],
|
|
28
|
+
[255, 0, 0],
|
|
29
|
+
[0, 255, 0],
|
|
30
|
+
[255, 255, 0],
|
|
31
|
+
[0, 0, 255],
|
|
32
|
+
[255, 0, 255],
|
|
33
|
+
[0, 255, 255],
|
|
34
|
+
[255, 255, 255]
|
|
18
35
|
];
|
|
19
36
|
|
|
20
37
|
const toLinear = (v: number): number => {
|
|
@@ -25,7 +42,6 @@ const toLinear = (v: number): number => {
|
|
|
25
42
|
const ESC = '\x1b';
|
|
26
43
|
|
|
27
44
|
export class StyleUtil {
|
|
28
|
-
|
|
29
45
|
/** Compute RGB values for ANSI 256 color code */
|
|
30
46
|
static computeRGBForAnsi256(code: number): [number, number, number] {
|
|
31
47
|
if (code < 16) {
|
|
@@ -49,7 +65,7 @@ export class StyleUtil {
|
|
|
49
65
|
return 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);
|
|
50
66
|
}
|
|
51
67
|
|
|
52
|
-
static #scheme: { key: string
|
|
68
|
+
static #scheme: { key: string; dark: boolean } = { key: '', dark: true };
|
|
53
69
|
|
|
54
70
|
/**
|
|
55
71
|
* Create text render function from style input using current color levels
|
|
@@ -61,9 +77,15 @@ export class StyleUtil {
|
|
|
61
77
|
let style = chalk;
|
|
62
78
|
for (const key of TypedObject.keys(input)) {
|
|
63
79
|
switch (key) {
|
|
64
|
-
case 'text':
|
|
65
|
-
|
|
66
|
-
|
|
80
|
+
case 'text':
|
|
81
|
+
style = style.hex(input[key]!);
|
|
82
|
+
break;
|
|
83
|
+
case 'background':
|
|
84
|
+
style = style.bgHex(input[key]!);
|
|
85
|
+
break;
|
|
86
|
+
default:
|
|
87
|
+
style = input[key] ? style[key] : style;
|
|
88
|
+
break;
|
|
67
89
|
}
|
|
68
90
|
}
|
|
69
91
|
return style;
|
|
@@ -106,8 +128,7 @@ export class StyleUtil {
|
|
|
106
128
|
* Build style palette, with support for background theme awareness
|
|
107
129
|
*/
|
|
108
130
|
static getPalette<K extends string>(input: Record<K, TermStylePairInput>): Record<K, TermStyleFn> {
|
|
109
|
-
return TypedObject.fromEntries(
|
|
110
|
-
TypedObject.entries(input).map(([key, value]) => [key, this.getThemedStyle(value)]));
|
|
131
|
+
return TypedObject.fromEntries(TypedObject.entries(input).map(([key, value]) => [key, this.getThemedStyle(value)]));
|
|
111
132
|
}
|
|
112
133
|
|
|
113
134
|
/**
|
|
@@ -149,4 +170,4 @@ export class StyleUtil {
|
|
|
149
170
|
return `${ESC}]8;;${url}${ESC}\\${text}${ESC}]8;;${ESC}\\`;
|
|
150
171
|
}
|
|
151
172
|
}
|
|
152
|
-
}
|
|
173
|
+
}
|
package/src/terminal.ts
CHANGED
|
@@ -4,8 +4,8 @@ import { Env, Util } from '@travetto/runtime';
|
|
|
4
4
|
|
|
5
5
|
import { TerminalWriter } from './writer.ts';
|
|
6
6
|
|
|
7
|
-
type TerminalStreamingConfig = { minDelay?: number
|
|
8
|
-
type Coord = { x: number
|
|
7
|
+
type TerminalStreamingConfig = { minDelay?: number; outputStreamToMain?: boolean };
|
|
8
|
+
type Coord = { x: number; y: number };
|
|
9
9
|
|
|
10
10
|
export const WAIT_TOKEN = '%WAIT%';
|
|
11
11
|
const STD_WAIT_STATES = '⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'.split('');
|
|
@@ -15,7 +15,6 @@ const lineMain = (line: string): string => line.replace(WAIT_TOKEN, '').trim();
|
|
|
15
15
|
|
|
16
16
|
/** An basic tty wrapper */
|
|
17
17
|
export class Terminal {
|
|
18
|
-
|
|
19
18
|
#interactive: boolean;
|
|
20
19
|
#writer: TerminalWriter;
|
|
21
20
|
#width: number;
|
|
@@ -24,15 +23,18 @@ export class Terminal {
|
|
|
24
23
|
|
|
25
24
|
async #showWaitingIndicator(position: Coord, signal: AbortSignal): Promise<void> {
|
|
26
25
|
let done = false;
|
|
27
|
-
signal.addEventListener('abort', () => done = true);
|
|
26
|
+
signal.addEventListener('abort', () => (done = true));
|
|
28
27
|
let i = 0;
|
|
29
28
|
while (!done) {
|
|
30
|
-
await this.#writer
|
|
29
|
+
await this.#writer
|
|
30
|
+
.setPosition(position)
|
|
31
|
+
.write(STD_WAIT_STATES[(i += 1 % STD_WAIT_STATES.length)])
|
|
32
|
+
.commit(true);
|
|
31
33
|
await Util.blockingTimeout(100);
|
|
32
34
|
}
|
|
33
35
|
}
|
|
34
36
|
|
|
35
|
-
constructor(output?: tty.WriteStream, config?: { width?: number
|
|
37
|
+
constructor(output?: tty.WriteStream, config?: { width?: number; height?: number }) {
|
|
36
38
|
this.#output = output ?? process.stdout;
|
|
37
39
|
this.#interactive = this.#output.isTTY && !Env.TRV_QUIET.isTrue;
|
|
38
40
|
this.#width = config?.width ?? (this.#output.isTTY ? this.#output.columns : 120);
|
|
@@ -40,11 +42,21 @@ export class Terminal {
|
|
|
40
42
|
this.#writer = new TerminalWriter(this);
|
|
41
43
|
}
|
|
42
44
|
|
|
43
|
-
get output(): tty.WriteStream {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
get
|
|
47
|
-
|
|
45
|
+
get output(): tty.WriteStream {
|
|
46
|
+
return this.#output;
|
|
47
|
+
}
|
|
48
|
+
get width(): number {
|
|
49
|
+
return this.#width;
|
|
50
|
+
}
|
|
51
|
+
get height(): number {
|
|
52
|
+
return this.#height;
|
|
53
|
+
}
|
|
54
|
+
get writer(): TerminalWriter {
|
|
55
|
+
return this.#writer;
|
|
56
|
+
}
|
|
57
|
+
get interactive(): boolean {
|
|
58
|
+
return this.#interactive;
|
|
59
|
+
}
|
|
48
60
|
|
|
49
61
|
/**
|
|
50
62
|
* Stream lines if interactive, with waiting indicator, otherwise print out
|
|
@@ -57,7 +69,10 @@ export class Terminal {
|
|
|
57
69
|
}
|
|
58
70
|
}
|
|
59
71
|
} else {
|
|
60
|
-
await this.streamToBottom(
|
|
72
|
+
await this.streamToBottom(
|
|
73
|
+
Util.mapAsyncIterable(source, line => `%WAIT% ${line}`),
|
|
74
|
+
{ outputStreamToMain: true }
|
|
75
|
+
);
|
|
61
76
|
}
|
|
62
77
|
}
|
|
63
78
|
|
|
@@ -73,9 +88,13 @@ export class Terminal {
|
|
|
73
88
|
let start = Date.now();
|
|
74
89
|
|
|
75
90
|
try {
|
|
76
|
-
await this.#writer
|
|
77
|
-
.
|
|
78
|
-
.
|
|
91
|
+
await this.#writer
|
|
92
|
+
.hideCursor()
|
|
93
|
+
.storePosition()
|
|
94
|
+
.scrollRange({ end: -2 })
|
|
95
|
+
.restorePosition()
|
|
96
|
+
.changePosition({ y: -1 })
|
|
97
|
+
.write('\n')
|
|
79
98
|
.commit();
|
|
80
99
|
|
|
81
100
|
for await (const line of source) {
|
|
@@ -84,7 +103,7 @@ export class Terminal {
|
|
|
84
103
|
await this.writer.writeLine(lineMain(previous)).commit();
|
|
85
104
|
}
|
|
86
105
|
|
|
87
|
-
if (line &&
|
|
106
|
+
if (line && Date.now() - start >= minDelay) {
|
|
88
107
|
start = Date.now();
|
|
89
108
|
stop?.abort();
|
|
90
109
|
this.writer.setPosition(writePosition).write(lineStatus(line)).clearLine(1).commit(true);
|
|
@@ -113,7 +132,7 @@ export class Terminal {
|
|
|
113
132
|
/**
|
|
114
133
|
* Consumes a stream, of events, tied to specific list indices, and updates in place
|
|
115
134
|
*/
|
|
116
|
-
async streamList(source: AsyncIterable<{ idx: number
|
|
135
|
+
async streamList(source: AsyncIterable<{ idx: number; text: string; done?: boolean }>): Promise<void> {
|
|
117
136
|
if (!this.#interactive) {
|
|
118
137
|
const collected = [];
|
|
119
138
|
for await (const event of source) {
|
|
@@ -133,7 +152,11 @@ export class Terminal {
|
|
|
133
152
|
await this.#writer.write('\n'.repeat(idx)).setPosition({ x: 0 }).write(text).clearLine(1).changePosition({ y: -idx }).commit();
|
|
134
153
|
}
|
|
135
154
|
} finally {
|
|
136
|
-
await this.#writer
|
|
155
|
+
await this.#writer
|
|
156
|
+
.changePosition({ y: max + 1 })
|
|
157
|
+
.writeLine('\n')
|
|
158
|
+
.reset()
|
|
159
|
+
.commit();
|
|
137
160
|
}
|
|
138
161
|
}
|
|
139
|
-
}
|
|
162
|
+
}
|
package/src/trv.d.ts
CHANGED
|
@@ -6,16 +6,16 @@ declare module '@travetto/runtime' {
|
|
|
6
6
|
* Flag for node to disable colors
|
|
7
7
|
*/
|
|
8
8
|
NODE_DISABLE_COLORS: boolean;
|
|
9
|
-
/**
|
|
9
|
+
/**
|
|
10
10
|
* Terminal colors provided as ansi 256 color schemes
|
|
11
11
|
*/
|
|
12
12
|
COLORFGBG: string;
|
|
13
|
-
/**
|
|
14
|
-
* Enables color, even if `tty` is not available
|
|
13
|
+
/**
|
|
14
|
+
* Enables color, even if `tty` is not available
|
|
15
15
|
* @default undefined
|
|
16
16
|
*/
|
|
17
17
|
FORCE_COLOR: boolean | ColorLevel;
|
|
18
|
-
/**
|
|
18
|
+
/**
|
|
19
19
|
* Disables color even if `tty` is available
|
|
20
20
|
* @default false
|
|
21
21
|
*/
|
|
@@ -25,8 +25,8 @@ declare module '@travetto/runtime' {
|
|
|
25
25
|
*/
|
|
26
26
|
COLORTERM: string;
|
|
27
27
|
/**
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
* Terminal operation mode, false means simple output
|
|
29
|
+
*/
|
|
30
30
|
TRV_QUIET: boolean;
|
|
31
31
|
}
|
|
32
|
-
}
|
|
32
|
+
}
|
package/src/util.ts
CHANGED
|
@@ -1,10 +1,35 @@
|
|
|
1
|
+
import rl from 'node:readline/promises';
|
|
2
|
+
|
|
3
|
+
import { Env } from '@travetto/runtime';
|
|
4
|
+
|
|
1
5
|
import { StyleUtil, type TermStyleFn, type TermStyleInput } from './style.ts';
|
|
2
6
|
import { type Terminal, WAIT_TOKEN } from './terminal.ts';
|
|
3
7
|
|
|
4
|
-
type ProgressEvent<T> = { total?: number
|
|
5
|
-
type ProgressStyle = { complete: TermStyleFn
|
|
8
|
+
export type ProgressEvent<T> = { total?: number; completed: number; value: T; failed?: number };
|
|
9
|
+
type ProgressStyle = { complete: TermStyleFn; failed: TermStyleFn; incomplete?: TermStyleFn };
|
|
6
10
|
|
|
7
11
|
export class TerminalUtil {
|
|
12
|
+
/**
|
|
13
|
+
* Determine if the terminal session is interactive
|
|
14
|
+
*/
|
|
15
|
+
static isInteractive(): boolean {
|
|
16
|
+
return process.stdout.isTTY && process.stdin.isTTY && !Env.TRV_QUIET.isTrue;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Prompt the user for input
|
|
21
|
+
*/
|
|
22
|
+
static async prompt(message: string): Promise<string> {
|
|
23
|
+
if (!this.isInteractive()) {
|
|
24
|
+
return '';
|
|
25
|
+
}
|
|
26
|
+
const reader = rl.createInterface({ input: process.stdin, output: process.stdout });
|
|
27
|
+
try {
|
|
28
|
+
return (await reader.question(message)).trim();
|
|
29
|
+
} finally {
|
|
30
|
+
reader.close();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
8
33
|
|
|
9
34
|
/**
|
|
10
35
|
* Create a progress bar updater, suitable for streaming to the bottom of the screen
|
|
@@ -13,31 +38,41 @@ export class TerminalUtil {
|
|
|
13
38
|
term: Terminal,
|
|
14
39
|
config?: {
|
|
15
40
|
withWaiting?: boolean;
|
|
16
|
-
style?: { complete: TermStyleInput
|
|
41
|
+
style?: { complete: TermStyleInput; failed?: TermStyleInput; incomplete?: TermStyleInput } | (() => ProgressStyle);
|
|
17
42
|
}
|
|
18
43
|
): (event: ProgressEvent<string>) => string {
|
|
19
|
-
const styleBase =
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
44
|
+
const styleBase =
|
|
45
|
+
typeof config?.style !== 'function'
|
|
46
|
+
? {
|
|
47
|
+
complete: StyleUtil.getStyle(config?.style?.complete ?? { background: '#248613', text: '#ffffff' }),
|
|
48
|
+
failed: StyleUtil.getStyle({ background: '#880000', text: '#ffffff', inverse: false }),
|
|
49
|
+
incomplete: config?.style?.incomplete ? StyleUtil.getStyle(config.style.incomplete) : undefined
|
|
50
|
+
}
|
|
51
|
+
: undefined;
|
|
23
52
|
|
|
24
53
|
const style = typeof config?.style === 'function' ? config.style : (): ProgressStyle => styleBase!;
|
|
25
54
|
|
|
26
55
|
let width: number;
|
|
27
56
|
return event => {
|
|
28
|
-
const text = event.value ?? (event.total ? '%
|
|
29
|
-
const progress = event.total === undefined ? 0 :
|
|
57
|
+
const text = event.value ?? (event.total ? '%completed/%total' : '%completed');
|
|
58
|
+
const progress = event.total === undefined ? 0 : event.completed / event.total;
|
|
30
59
|
if (event.total) {
|
|
31
60
|
width ??= Math.trunc(Math.ceil(Math.log10(event.total ?? 10000)));
|
|
32
61
|
}
|
|
33
|
-
const state: Record<string, string> = {
|
|
34
|
-
|
|
62
|
+
const state: Record<string, string> = {
|
|
63
|
+
total: `${event.total}`,
|
|
64
|
+
completed: `${event.completed}`.padStart(width ?? 0),
|
|
65
|
+
progress: `${Math.trunc(progress * 100)}`,
|
|
66
|
+
failed: `${event.failed ?? 0}`
|
|
67
|
+
};
|
|
68
|
+
const line = ` ${text.replace(/[%](completed|total|progress|failed)/g, (_, key) => state[key])} `;
|
|
35
69
|
const full = term.writer.padToWidth(line, config?.withWaiting ? 2 : 0);
|
|
36
70
|
const mid = Math.trunc(progress * term.width);
|
|
37
71
|
const [left, right] = [full.substring(0, mid), full.substring(mid)];
|
|
38
72
|
|
|
39
|
-
const { complete, incomplete } = style();
|
|
40
|
-
|
|
73
|
+
const { complete, incomplete, failed } = style();
|
|
74
|
+
const color = event.failed ? failed : complete;
|
|
75
|
+
return `${config?.withWaiting ? `${WAIT_TOKEN} ` : ''}${color(left)}${incomplete?.(right) ?? right}`;
|
|
41
76
|
};
|
|
42
77
|
}
|
|
43
|
-
}
|
|
78
|
+
}
|
package/src/writer.ts
CHANGED
|
@@ -2,8 +2,8 @@ import type tty from 'node:tty';
|
|
|
2
2
|
|
|
3
3
|
import { ShutdownManager } from '@travetto/runtime';
|
|
4
4
|
|
|
5
|
-
type State = { output: tty.WriteStream
|
|
6
|
-
type TermCoord = { x: number
|
|
5
|
+
type State = { output: tty.WriteStream; height: number; width: number };
|
|
6
|
+
type TermCoord = { x: number; y: number };
|
|
7
7
|
|
|
8
8
|
const ESC = '\x1b[';
|
|
9
9
|
const clamp = (input: number, size: number): number => Math.max(Math.min(input + (input < 0 ? size : 0), size - 1), 0);
|
|
@@ -23,10 +23,9 @@ const Codes = {
|
|
|
23
23
|
|
|
24
24
|
DEBUG: (text: string): string => text.replaceAll(ESC, '<ESC>').replaceAll('\n', '<NL>'),
|
|
25
25
|
CURSOR_MOVE: (x?: number, y?: number): string => `${delta(x, 'C', 'D')}${delta(y, 'B', 'A')}`,
|
|
26
|
-
POSITION_SET: (x: number, y: number | undefined, maxX: number, maxY: number): string =>
|
|
27
|
-
`${ESC}${clamp(y, maxY) + 1};${clamp(x, maxX) + 1}H` : `${ESC}${clamp(x, maxX) + 1}G`,
|
|
28
|
-
SCROLL_RANGE_SET: (start: number, end: number, max: number): string =>
|
|
29
|
-
`${ESC}${clamp(start, max) + 1};${clamp(end, max) + 1}r`
|
|
26
|
+
POSITION_SET: (x: number, y: number | undefined, maxX: number, maxY: number): string =>
|
|
27
|
+
y !== undefined ? `${ESC}${clamp(y, maxY) + 1};${clamp(x, maxX) + 1}H` : `${ESC}${clamp(x, maxX) + 1}G`,
|
|
28
|
+
SCROLL_RANGE_SET: (start: number, end: number, max: number): string => `${ESC}${clamp(start, max) + 1};${clamp(end, max) + 1}r`
|
|
30
29
|
};
|
|
31
30
|
|
|
32
31
|
/**
|
|
@@ -63,7 +62,7 @@ export class TerminalWriter {
|
|
|
63
62
|
|
|
64
63
|
/** Pad to width of terminal */
|
|
65
64
|
padToWidth(text: string, offset = 0, ellipsis = '...'): string {
|
|
66
|
-
if (text.length >
|
|
65
|
+
if (text.length > this.#term.width - offset) {
|
|
67
66
|
return `${text.substring(0, this.#term.width - (offset + ellipsis.length))}${ellipsis}`;
|
|
68
67
|
}
|
|
69
68
|
return text.padEnd(this.#term.width - offset, ' ');
|
|
@@ -107,9 +106,12 @@ export class TerminalWriter {
|
|
|
107
106
|
/** Clear line, -1 (left), 0 (both), 1 (right), from current cursor */
|
|
108
107
|
clearLine(dir: -1 | 0 | 1 = 0): this {
|
|
109
108
|
switch (dir) {
|
|
110
|
-
case 0:
|
|
111
|
-
|
|
112
|
-
case
|
|
109
|
+
case 0:
|
|
110
|
+
return this.write(Codes.ERASE_LINE_ALL);
|
|
111
|
+
case 1:
|
|
112
|
+
return this.write(Codes.ERASE_LINE_RIGHT);
|
|
113
|
+
case -1:
|
|
114
|
+
return this.write(Codes.ERASE_LINE_LEFT);
|
|
113
115
|
}
|
|
114
116
|
}
|
|
115
117
|
|
|
@@ -152,7 +154,7 @@ export class TerminalWriter {
|
|
|
152
154
|
}
|
|
153
155
|
|
|
154
156
|
/** Set scrolling range */
|
|
155
|
-
scrollRange({ start = 0, end = -1 }: { start?: number
|
|
157
|
+
scrollRange({ start = 0, end = -1 }: { start?: number; end?: number }): this {
|
|
156
158
|
return this.trackDirty(true).write(Codes.SCROLL_RANGE_SET(start, end, this.#term.height));
|
|
157
159
|
}
|
|
158
160
|
|
|
@@ -165,4 +167,4 @@ export class TerminalWriter {
|
|
|
165
167
|
reset(): this {
|
|
166
168
|
return this.trackDirty(false).write(Codes.SOFT_RESET).write(Codes.SHOW_CURSOR);
|
|
167
169
|
}
|
|
168
|
-
}
|
|
170
|
+
}
|