@travetto/terminal 7.0.0-rc.1 → 7.0.0-rc.2
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/package.json +2 -2
- package/src/style.ts +10 -10
- package/src/terminal.ts +18 -18
- package/src/util.ts +17 -17
- package/src/writer.ts +10 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/terminal",
|
|
3
|
-
"version": "7.0.0-rc.
|
|
3
|
+
"version": "7.0.0-rc.2",
|
|
4
4
|
"description": "General terminal support",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"terminal",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"directory": "module/terminal"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@travetto/runtime": "^7.0.0-rc.
|
|
27
|
+
"@travetto/runtime": "^7.0.0-rc.2",
|
|
28
28
|
"chalk": "^5.6.2"
|
|
29
29
|
},
|
|
30
30
|
"travetto": {
|
package/src/style.ts
CHANGED
|
@@ -43,7 +43,7 @@ export class StyleUtil {
|
|
|
43
43
|
* Read foreground/background color if env var is set
|
|
44
44
|
*/
|
|
45
45
|
static isBackgroundDark(): boolean {
|
|
46
|
-
const key = Env.COLORFGBG.
|
|
46
|
+
const key = Env.COLORFGBG.value ?? '';
|
|
47
47
|
|
|
48
48
|
if (this.#scheme.key === key) {
|
|
49
49
|
return this.#scheme.dark;
|
|
@@ -74,9 +74,9 @@ export class StyleUtil {
|
|
|
74
74
|
/**
|
|
75
75
|
* Build style palette, with support for background theme awareness
|
|
76
76
|
*/
|
|
77
|
-
static getPalette<K extends string>(
|
|
77
|
+
static getPalette<K extends string>(input: Record<K, TermStylePairInput>): Record<K, TermStyleFn> {
|
|
78
78
|
return TypedObject.fromEntries(
|
|
79
|
-
TypedObject.entries(
|
|
79
|
+
TypedObject.entries(input).map(([key, value]) => [key, this.getThemedStyle(value)]));
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
/**
|
|
@@ -89,16 +89,16 @@ export class StyleUtil {
|
|
|
89
89
|
if (keys.length === 0) {
|
|
90
90
|
return values[0];
|
|
91
91
|
} else {
|
|
92
|
-
const out = keys.map((
|
|
93
|
-
let final =
|
|
94
|
-
if (typeof
|
|
95
|
-
const subKeys = TypedObject.keys(
|
|
92
|
+
const out = keys.map((item, i) => {
|
|
93
|
+
let final = item;
|
|
94
|
+
if (typeof item !== 'string') {
|
|
95
|
+
const subKeys = TypedObject.keys(item);
|
|
96
96
|
if (subKeys.length !== 1) {
|
|
97
97
|
throw new Error('Invalid template variable, one and only one key should be specified');
|
|
98
98
|
}
|
|
99
|
-
const [
|
|
100
|
-
const
|
|
101
|
-
final =
|
|
99
|
+
const [key] = subKeys;
|
|
100
|
+
const value = item[key]!;
|
|
101
|
+
final = value === undefined ? '' : palette[key](value)!;
|
|
102
102
|
}
|
|
103
103
|
return `${values[i] ?? ''}${final ?? ''}`;
|
|
104
104
|
});
|
package/src/terminal.ts
CHANGED
|
@@ -10,8 +10,8 @@ type Coord = { x: number, y: number };
|
|
|
10
10
|
export const WAIT_TOKEN = '%WAIT%';
|
|
11
11
|
const STD_WAIT_STATES = '⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'.split('');
|
|
12
12
|
|
|
13
|
-
const lineStatus = (
|
|
14
|
-
const lineMain = (
|
|
13
|
+
const lineStatus = (line: string): string => line.replace(WAIT_TOKEN, ' ');
|
|
14
|
+
const lineMain = (line: string): string => line.replace(WAIT_TOKEN, '').trim();
|
|
15
15
|
|
|
16
16
|
/** An basic tty wrapper */
|
|
17
17
|
export class Terminal {
|
|
@@ -22,12 +22,12 @@ export class Terminal {
|
|
|
22
22
|
#height: number;
|
|
23
23
|
#output: tty.WriteStream;
|
|
24
24
|
|
|
25
|
-
async #showWaitingIndicator(
|
|
25
|
+
async #showWaitingIndicator(position: Coord, signal: AbortSignal): Promise<void> {
|
|
26
26
|
let done = false;
|
|
27
27
|
signal.addEventListener('abort', () => done = true);
|
|
28
28
|
let i = 0;
|
|
29
29
|
while (!done) {
|
|
30
|
-
await this.#writer.setPosition(
|
|
30
|
+
await this.#writer.setPosition(position).write(STD_WAIT_STATES[i++ % STD_WAIT_STATES.length]).commit(true);
|
|
31
31
|
await Util.blockingTimeout(100);
|
|
32
32
|
}
|
|
33
33
|
}
|
|
@@ -57,7 +57,7 @@ export class Terminal {
|
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
} else {
|
|
60
|
-
await this.streamToBottom(Util.
|
|
60
|
+
await this.streamToBottom(Util.mapAsyncIterable(source, line => `%WAIT% ${line}`), { outputStreamToMain: true });
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
|
|
@@ -65,10 +65,10 @@ export class Terminal {
|
|
|
65
65
|
* Allows for writing at bottom of screen with scrolling support for main content
|
|
66
66
|
*/
|
|
67
67
|
async streamToBottom(source: AsyncIterable<string | undefined>, config: TerminalStreamingConfig = {}): Promise<void> {
|
|
68
|
-
const
|
|
68
|
+
const writePosition = { x: 0, y: -1 };
|
|
69
69
|
const minDelay = config.minDelay ?? 0;
|
|
70
70
|
|
|
71
|
-
let
|
|
71
|
+
let previous: string | undefined;
|
|
72
72
|
let stop: AbortController | undefined;
|
|
73
73
|
let start = Date.now();
|
|
74
74
|
|
|
@@ -80,31 +80,31 @@ export class Terminal {
|
|
|
80
80
|
|
|
81
81
|
for await (const line of source) {
|
|
82
82
|
// Previous line
|
|
83
|
-
if (
|
|
84
|
-
await this.writer.writeLine(lineMain(
|
|
83
|
+
if (previous && config.outputStreamToMain) {
|
|
84
|
+
await this.writer.writeLine(lineMain(previous)).commit();
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
if (line && (Date.now() - start) >= minDelay) {
|
|
88
88
|
start = Date.now();
|
|
89
89
|
stop?.abort();
|
|
90
|
-
this.writer.setPosition(
|
|
90
|
+
this.writer.setPosition(writePosition).write(lineStatus(line)).clearLine(1).commit(true);
|
|
91
91
|
|
|
92
92
|
const idx = line.indexOf(WAIT_TOKEN);
|
|
93
93
|
if (idx >= 0) {
|
|
94
94
|
stop = new AbortController();
|
|
95
|
-
this.#showWaitingIndicator({ y:
|
|
95
|
+
this.#showWaitingIndicator({ y: writePosition.y, x: idx }, stop.signal);
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
|
|
99
|
+
previous = line;
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
stop?.abort();
|
|
103
|
-
if (
|
|
104
|
-
await this.writer.writeLine(lineMain(
|
|
103
|
+
if (previous !== undefined && config.outputStreamToMain) {
|
|
104
|
+
await this.writer.writeLine(lineMain(previous)).commit();
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
await this.#writer.setPosition(
|
|
107
|
+
await this.#writer.setPosition(writePosition).clearLine().commit(true);
|
|
108
108
|
} finally {
|
|
109
109
|
await this.#writer.reset().commit();
|
|
110
110
|
}
|
|
@@ -116,9 +116,9 @@ export class Terminal {
|
|
|
116
116
|
async streamList(source: AsyncIterable<{ idx: number, text: string, done?: boolean }>): Promise<void> {
|
|
117
117
|
if (!this.#interactive) {
|
|
118
118
|
const collected = [];
|
|
119
|
-
for await (const
|
|
120
|
-
if (
|
|
121
|
-
collected[
|
|
119
|
+
for await (const event of source) {
|
|
120
|
+
if (event.done) {
|
|
121
|
+
collected[event.idx] = event.text;
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
124
|
await this.#writer.writeLines(collected).commit();
|
package/src/util.ts
CHANGED
|
@@ -11,33 +11,33 @@ export class TerminalUtil {
|
|
|
11
11
|
*/
|
|
12
12
|
static progressBarUpdater(
|
|
13
13
|
term: Terminal,
|
|
14
|
-
|
|
14
|
+
config?: {
|
|
15
15
|
withWaiting?: boolean;
|
|
16
16
|
style?: { complete: TermStyleInput, incomplete?: TermStyleInput } | (() => ProgressStyle);
|
|
17
17
|
}
|
|
18
|
-
): (
|
|
19
|
-
const styleBase = typeof
|
|
20
|
-
complete: StyleUtil.getStyle(
|
|
21
|
-
incomplete:
|
|
18
|
+
): (event: ProgressEvent<string>) => string {
|
|
19
|
+
const styleBase = typeof config?.style !== 'function' ? {
|
|
20
|
+
complete: StyleUtil.getStyle(config?.style?.complete ?? { background: '#248613', text: '#ffffff' }),
|
|
21
|
+
incomplete: config?.style?.incomplete ? StyleUtil.getStyle(config.style.incomplete) : undefined,
|
|
22
22
|
} : undefined;
|
|
23
23
|
|
|
24
|
-
const style = typeof
|
|
24
|
+
const style = typeof config?.style === 'function' ? config.style : (): ProgressStyle => styleBase!;
|
|
25
25
|
|
|
26
26
|
let width: number;
|
|
27
|
-
return
|
|
28
|
-
const text =
|
|
29
|
-
const
|
|
30
|
-
if (
|
|
31
|
-
width ??= Math.trunc(Math.ceil(Math.log10(
|
|
27
|
+
return event => {
|
|
28
|
+
const text = event.value ?? (event.total ? '%idx/%total' : '%idx');
|
|
29
|
+
const progress = event.total === undefined ? 0 : (event.idx / event.total);
|
|
30
|
+
if (event.total) {
|
|
31
|
+
width ??= Math.trunc(Math.ceil(Math.log10(event.total ?? 10000)));
|
|
32
32
|
}
|
|
33
|
-
const state: Record<string, string> = { total: `${
|
|
34
|
-
const line = ` ${text.replace(/[%](idx|total|
|
|
35
|
-
const full = term.writer.padToWidth(line,
|
|
36
|
-
const mid = Math.trunc(
|
|
37
|
-
const [
|
|
33
|
+
const state: Record<string, string> = { total: `${event.total}`, idx: `${event.idx}`.padStart(width ?? 0), progress: `${Math.trunc(progress * 100)}` };
|
|
34
|
+
const line = ` ${text.replace(/[%](idx|total|progress)/g, (_, key) => state[key])} `;
|
|
35
|
+
const full = term.writer.padToWidth(line, config?.withWaiting ? 2 : 0);
|
|
36
|
+
const mid = Math.trunc(progress * term.width);
|
|
37
|
+
const [left, right] = [full.substring(0, mid), full.substring(mid)];
|
|
38
38
|
|
|
39
39
|
const { complete, incomplete } = style();
|
|
40
|
-
return `${
|
|
40
|
+
return `${config?.withWaiting ? `${WAIT_TOKEN} ` : ''}${complete(left)}${incomplete?.(right) ?? right}`;
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
43
|
}
|
package/src/writer.ts
CHANGED
|
@@ -6,9 +6,9 @@ type State = { output: tty.WriteStream, height: number, width: number };
|
|
|
6
6
|
type TermCoord = { x: number, y: number };
|
|
7
7
|
|
|
8
8
|
const ESC = '\x1b[';
|
|
9
|
-
const clamp = (
|
|
10
|
-
const delta = (
|
|
11
|
-
!
|
|
9
|
+
const clamp = (input: number, size: number): number => Math.max(Math.min(input + (input < 0 ? size : 0), size - 1), 0);
|
|
10
|
+
const delta = (input: number | undefined, position: string, negative: string): string =>
|
|
11
|
+
!input ? '' : `${ESC}${Math.abs(input)}${input < 0 ? negative : position}`;
|
|
12
12
|
|
|
13
13
|
const Codes = {
|
|
14
14
|
SHOW_CURSOR: `${ESC}?25h`,
|
|
@@ -73,14 +73,14 @@ export class TerminalWriter {
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
commit(restorePosition: boolean = this.#restoreOnCommit): Promise<void> {
|
|
76
|
-
const
|
|
76
|
+
const queue = this.#buffer.filter(line => line !== undefined);
|
|
77
77
|
this.#buffer = [];
|
|
78
|
-
if (
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
if (queue.length && restorePosition) {
|
|
79
|
+
queue.unshift(Codes.POSITION_SAVE);
|
|
80
|
+
queue.push(Codes.POSITION_RESTORE);
|
|
81
81
|
}
|
|
82
|
-
if (
|
|
83
|
-
return new Promise<void>(
|
|
82
|
+
if (queue.length && !this.#term.output.write(queue.join(''))) {
|
|
83
|
+
return new Promise<void>(resolve => this.#term.output.once('drain', resolve));
|
|
84
84
|
} else {
|
|
85
85
|
return Promise.resolve();
|
|
86
86
|
}
|
|
@@ -127,7 +127,7 @@ export class TerminalWriter {
|
|
|
127
127
|
|
|
128
128
|
/** Write multiple lines */
|
|
129
129
|
writeLines(lines: (string | undefined)[], clear = false): this {
|
|
130
|
-
lines = lines.filter(
|
|
130
|
+
lines = lines.filter(line => line !== undefined);
|
|
131
131
|
let text = lines.join('\n');
|
|
132
132
|
if (text.length > 0) {
|
|
133
133
|
if (clear) {
|