@travetto/terminal 3.4.0 → 4.0.0-rc.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 +17 -20
- package/__index__.ts +4 -9
- package/package.json +5 -1
- package/src/style.ts +122 -0
- package/src/terminal.ts +116 -169
- package/src/trv.d.ts +32 -0
- package/src/util.ts +43 -0
- package/src/writer.ts +65 -71
- package/src/codes.ts +0 -41
- package/src/color-define.ts +0 -195
- package/src/color-output.ts +0 -128
- package/src/iterable.ts +0 -100
- package/src/named-colors.ts +0 -160
- package/src/operation.ts +0 -158
- package/src/query.ts +0 -94
- package/src/types.ts +0 -33
package/src/writer.ts
CHANGED
|
@@ -1,28 +1,50 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
import tty from 'node:tty';
|
|
2
|
+
|
|
3
|
+
type State = { output: tty.WriteStream, height: number, width: number };
|
|
4
|
+
type TermCoord = { x: number, y: number };
|
|
5
|
+
|
|
6
|
+
const ESC = '\x1b[';
|
|
7
|
+
const clamp = (v: number, size: number): number => Math.max(Math.min(v + (v < 0 ? size : 0), size - 1), 0);
|
|
8
|
+
const delta = (v: number | undefined, pos: string, neg: string): string =>
|
|
9
|
+
!v ? '' : `${ESC}${Math.abs(v)}${v < 0 ? neg : pos}`;
|
|
10
|
+
|
|
11
|
+
const Codes = {
|
|
12
|
+
SHOW_CURSOR: `${ESC}?25h`,
|
|
13
|
+
HIDE_CURSOR: `${ESC}?25l`,
|
|
14
|
+
SCROLL_RANGE_CLEAR: `${ESC}r`,
|
|
15
|
+
POSITION_RESTORE: `${ESC}u`,
|
|
16
|
+
POSITION_SAVE: `${ESC}s`,
|
|
17
|
+
SOFT_RESET_CODES: `${ESC}!p`,
|
|
18
|
+
ERASE_LINE_RIGHT: `${ESC}0K`,
|
|
19
|
+
ERASE_LINE_LEFT: `${ESC}1K`,
|
|
20
|
+
ERASE_LINE_ALL: `${ESC}2K`,
|
|
21
|
+
|
|
22
|
+
DEBUG: (text: string): string => text.replaceAll(ESC, '<ESC>').replaceAll('\n', '<NL>'),
|
|
23
|
+
CURSOR_MOVE: (x?: number, y?: number): string => `${delta(x, 'C', 'D')}${delta(y, 'B', 'A')}`,
|
|
24
|
+
POSITION_SET: (x: number, y: number | undefined, maxX: number, maxY: number): string => y !== undefined ?
|
|
25
|
+
`${ESC}${clamp(y, maxY) + 1};${clamp(x, maxX) + 1}H` : `${ESC}${clamp(x, maxX) + 1}G`,
|
|
26
|
+
SCROLL_RANGE_SET: (start: number, end: number, max: number): string =>
|
|
27
|
+
`${ESC}${clamp(start, max) + 1};${clamp(end, max) + 1}r`
|
|
9
28
|
};
|
|
10
29
|
|
|
11
30
|
/**
|
|
12
31
|
* Buffered/batched writer. Meant to be similar to readline.Readline, but with more general writing support and extensibility
|
|
13
32
|
*/
|
|
14
33
|
export class TerminalWriter {
|
|
34
|
+
#buffer: (string | number)[] = [];
|
|
35
|
+
#restoreOnCommit = false;
|
|
36
|
+
#term: State;
|
|
15
37
|
|
|
16
|
-
|
|
17
|
-
|
|
38
|
+
constructor(state: State) {
|
|
39
|
+
this.#term = state;
|
|
18
40
|
}
|
|
19
41
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
this.#term
|
|
42
|
+
/** Pad to width of terminal */
|
|
43
|
+
padToWidth(text: string, offset = 0, ellipsis = '...'): string {
|
|
44
|
+
if (text.length > (this.#term.width - offset)) {
|
|
45
|
+
return `${text.substring(0, this.#term.width - (offset + ellipsis.length))}${ellipsis}`;
|
|
46
|
+
}
|
|
47
|
+
return text.padEnd(this.#term.width - offset, ' ');
|
|
26
48
|
}
|
|
27
49
|
|
|
28
50
|
/** Restore on commit */
|
|
@@ -32,62 +54,51 @@ export class TerminalWriter {
|
|
|
32
54
|
}
|
|
33
55
|
|
|
34
56
|
commit(restorePosition: boolean = this.#restoreOnCommit): Promise<void> {
|
|
35
|
-
const q = this.#
|
|
36
|
-
this.#
|
|
57
|
+
const q = this.#buffer.filter(x => x !== undefined);
|
|
58
|
+
this.#buffer = [];
|
|
37
59
|
if (q.length && restorePosition) {
|
|
38
|
-
q.unshift(
|
|
39
|
-
q.push(
|
|
60
|
+
q.unshift(Codes.POSITION_SAVE);
|
|
61
|
+
q.push(Codes.POSITION_RESTORE);
|
|
62
|
+
}
|
|
63
|
+
if (q.length && !this.#term.output.write(q.join(''))) {
|
|
64
|
+
return new Promise<void>(r => this.#term.output.once('drain', r));
|
|
65
|
+
} else {
|
|
66
|
+
return Promise.resolve();
|
|
40
67
|
}
|
|
41
|
-
return q.length ? new Promise(r => this.#term.output.write(q.join(''), () => r())) : Promise.resolve();
|
|
42
68
|
}
|
|
43
69
|
|
|
44
70
|
write(...text: (string | number)[]): this {
|
|
45
|
-
this.#
|
|
71
|
+
this.#buffer.push(...text);
|
|
46
72
|
return this;
|
|
47
73
|
}
|
|
48
74
|
|
|
49
75
|
/** Stores current cursor position, if called multiple times before restore, last one ones */
|
|
50
76
|
storePosition(): this {
|
|
51
|
-
return this.write(
|
|
77
|
+
return this.write(Codes.POSITION_SAVE);
|
|
52
78
|
}
|
|
53
79
|
|
|
54
80
|
/** Restores cursor position, will not behave correctly if nested */
|
|
55
81
|
restorePosition(): this {
|
|
56
|
-
return this.write(
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/** Rewrite a single line in the stream */
|
|
60
|
-
rewriteLine(text: string): this {
|
|
61
|
-
return this.setPosition({ x: 0 }).write(text);
|
|
82
|
+
return this.write(Codes.POSITION_RESTORE);
|
|
62
83
|
}
|
|
63
84
|
|
|
64
85
|
/** Clear line, -1 (left), 0 (both), 1 (right), from current cursor */
|
|
65
86
|
clearLine(dir: -1 | 0 | 1 = 0): this {
|
|
66
|
-
|
|
87
|
+
switch (dir) {
|
|
88
|
+
case 0: return this.write(Codes.ERASE_LINE_ALL);
|
|
89
|
+
case 1: return this.write(Codes.ERASE_LINE_RIGHT);
|
|
90
|
+
case -1: return this.write(Codes.ERASE_LINE_LEFT);
|
|
91
|
+
}
|
|
67
92
|
}
|
|
68
93
|
|
|
69
94
|
/** Set position */
|
|
70
95
|
setPosition({ x = 0, y }: Partial<TermCoord>): this {
|
|
71
|
-
|
|
72
|
-
y = boundIndex(y, this.#term.output.rows);
|
|
73
|
-
}
|
|
74
|
-
x = boundIndex(x, this.#term.output.columns);
|
|
75
|
-
if (y !== undefined) {
|
|
76
|
-
return this.write(ANSICodes.POSITION_SET(y + 1, x + 1));
|
|
77
|
-
} else {
|
|
78
|
-
return this.write(ANSICodes.COLUMN_SET(x + 1));
|
|
79
|
-
}
|
|
96
|
+
return this.write(Codes.POSITION_SET(x, y, this.#term.width, this.#term.height));
|
|
80
97
|
}
|
|
81
98
|
|
|
82
99
|
/** Relative movement */
|
|
83
100
|
changePosition({ x, y }: Partial<TermCoord>): this {
|
|
84
|
-
|
|
85
|
-
this.write(ANSICodes.CURSOR_DX(x));
|
|
86
|
-
}
|
|
87
|
-
if (y) {
|
|
88
|
-
this.write(ANSICodes.CURSOR_DY(y));
|
|
89
|
-
}
|
|
90
|
-
return this;
|
|
101
|
+
return this.write(Codes.CURSOR_MOVE(x, y));
|
|
91
102
|
}
|
|
92
103
|
|
|
93
104
|
/** Write single line */
|
|
@@ -101,7 +112,7 @@ export class TerminalWriter {
|
|
|
101
112
|
let text = lines.join('\n');
|
|
102
113
|
if (text.length > 0) {
|
|
103
114
|
if (clear) {
|
|
104
|
-
text = text.replaceAll('\n', `${
|
|
115
|
+
text = text.replaceAll('\n', `${Codes.ERASE_LINE_RIGHT}\n`);
|
|
105
116
|
}
|
|
106
117
|
text = `${text}\n`;
|
|
107
118
|
}
|
|
@@ -110,43 +121,26 @@ export class TerminalWriter {
|
|
|
110
121
|
|
|
111
122
|
/** Show cursor */
|
|
112
123
|
showCursor(): this {
|
|
113
|
-
return this.write(
|
|
124
|
+
return this.write(Codes.SHOW_CURSOR);
|
|
114
125
|
}
|
|
115
126
|
|
|
116
127
|
/** Hide cursor */
|
|
117
128
|
hideCursor(): this {
|
|
118
|
-
return this.write(
|
|
129
|
+
return this.write(Codes.HIDE_CURSOR);
|
|
119
130
|
}
|
|
120
131
|
|
|
121
132
|
/** Set scrolling range */
|
|
122
|
-
scrollRange({ start, end }: { start?: number, end?: number }): this {
|
|
123
|
-
|
|
124
|
-
end = boundIndex(end ?? -1, this.#term.output.rows);
|
|
125
|
-
return this.write(ANSICodes.SCROLL_RANGE_SET(start + 1, end + 1));
|
|
133
|
+
scrollRange({ start = 0, end = -1 }: { start?: number, end?: number }): this {
|
|
134
|
+
return this.write(Codes.SCROLL_RANGE_SET(start, end, this.#term.height));
|
|
126
135
|
}
|
|
127
136
|
|
|
128
137
|
/** Clear scrolling range */
|
|
129
138
|
scrollRangeClear(): this {
|
|
130
|
-
return this.write(
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/** Scrolling window y <=0 - up, else down */
|
|
134
|
-
scrollY(y: number): this {
|
|
135
|
-
return this.write(ANSICodes.SCROLL_WINDOW(y));
|
|
139
|
+
return this.write(Codes.SCROLL_RANGE_CLEAR);
|
|
136
140
|
}
|
|
137
141
|
|
|
138
142
|
/** Reset */
|
|
139
|
-
|
|
140
|
-
return this.write(
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/** Reset Commands */
|
|
144
|
-
resetCommands(): string {
|
|
145
|
-
return [
|
|
146
|
-
ANSICodes.POSITION_SAVE(),
|
|
147
|
-
ANSICodes.SHOW_CURSOR(),
|
|
148
|
-
ANSICodes.SCROLL_RANGE_CLEAR(),
|
|
149
|
-
ANSICodes.POSITION_RESTORE()
|
|
150
|
-
].join('');
|
|
143
|
+
softReset(): this {
|
|
144
|
+
return this.write(Codes.SOFT_RESET_CODES);
|
|
151
145
|
}
|
|
152
146
|
}
|
package/src/codes.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
const ESC = '\x1b[';
|
|
2
|
-
const OSC = '\x1b]';
|
|
3
|
-
const ST = '\x1b\\';
|
|
4
|
-
|
|
5
|
-
// eslint-disable-next-line no-control-regex
|
|
6
|
-
export const ANSI_CODE_REGEX = /(\x1b|\x1B)[\[\]][?]?[0-9;]+[A-Za-z]/g;
|
|
7
|
-
|
|
8
|
-
export const OSC_QUERY_FIELDS = {
|
|
9
|
-
backgroundColor: 11,
|
|
10
|
-
foregroundColor: 10
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
export type OSCQueryField = keyof (typeof OSC_QUERY_FIELDS);
|
|
14
|
-
|
|
15
|
-
export const DEVICE_STATUS_FIELDS = {
|
|
16
|
-
cursorPosition: 6,
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
export type DeviceStatusField = keyof (typeof DEVICE_STATUS_FIELDS);
|
|
20
|
-
|
|
21
|
-
export const ANSICodes = {
|
|
22
|
-
DEBUG: (text: string): string => text.replaceAll(ESC, '<ESC>').replaceAll(OSC, '<OSC>').replaceAll('\n', '<NL>').replaceAll(ST, '<ST>'),
|
|
23
|
-
CURSOR_DY: (row: number): string => `${ESC}${Math.abs(row)}${row < 0 ? 'A' : 'B'}`,
|
|
24
|
-
CURSOR_DX: (col: number): string => `${ESC}${Math.abs(col)}${col < 0 ? 'D' : 'C'}`,
|
|
25
|
-
COLUMN_SET: (col: number): string => `${ESC}${col}G`,
|
|
26
|
-
POSITION_SET: (row: number, col: number): string => `${ESC}${row};${col}H`,
|
|
27
|
-
ERASE_SCREEN: (dir: 0 | 1 | 2 | 3 = 0): string => `${ESC}${dir}J`,
|
|
28
|
-
ERASE_LINE: (dir: 0 | 1 | 2 = 0): string => `${ESC}${dir}K`,
|
|
29
|
-
STYLE: (codes: (string | number)[]): string => `${ESC}${codes.join(';')}m`,
|
|
30
|
-
SHOW_CURSOR: (): string => `${ESC}?25h`,
|
|
31
|
-
HIDE_CURSOR: (): string => `${ESC}?25l`,
|
|
32
|
-
SCROLL_RANGE_SET: (start: number, end: number): string => `${ESC}${start};${end}r`,
|
|
33
|
-
SCROLL_RANGE_CLEAR: (): string => `${ESC}r`,
|
|
34
|
-
SCROLL_WINDOW: (rows: number): string => `${ESC}${Math.abs(rows)}${rows < 0 ? 'S' : 'T'}`,
|
|
35
|
-
POSITION_RESTORE: (): string => `${ESC}u`,
|
|
36
|
-
POSITION_SAVE: (): string => `${ESC}s`,
|
|
37
|
-
DEVICE_STATUS_REPORT: (code: DeviceStatusField): string => `${ESC}${DEVICE_STATUS_FIELDS[code]}n`,
|
|
38
|
-
OSC_QUERY: (code: OSCQueryField): string => `${OSC}${OSC_QUERY_FIELDS[code]};?${ST}`,
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
export const stripAnsiCodes = (text: string): string => text.replace(ANSI_CODE_REGEX, '');
|
package/src/color-define.ts
DELETED
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
import { NAMED_COLORS } from './named-colors';
|
|
2
|
-
import { RGB, TermColorScheme } from './types';
|
|
3
|
-
|
|
4
|
-
type I = number;
|
|
5
|
-
type HSL = [h: I, s: I, l: I];
|
|
6
|
-
export type DefinedColor = { rgb: RGB, hsl: HSL, idx16: I, idx16bg: I, idx256: I, scheme: TermColorScheme };
|
|
7
|
-
export type RGBInput = I | keyof (typeof NAMED_COLORS) | `#${string}`;
|
|
8
|
-
|
|
9
|
-
const _rgb = (r: I, g: I = r, b: I = g): RGB => [r, g, b];
|
|
10
|
-
|
|
11
|
-
const STD_PRE = ['std', 'bright'] as const;
|
|
12
|
-
const STD_COLORS = ['Black', 'Red', 'Green', 'Yellow', 'Blue', 'Magenta', 'Cyan', 'White'] as const;
|
|
13
|
-
|
|
14
|
-
// eslint-disable-next-line no-bitwise
|
|
15
|
-
const toRgbArray = (val: number): RGB => [(val >> 16) & 255, (val >> 8) & 255, val & 255];
|
|
16
|
-
|
|
17
|
-
const ANSI256_TO_16_IDX = [30, 31, 32, 33, 34, 35, 36, 37, 90, 91, 92, 93, 94, 95, 96, 97];
|
|
18
|
-
const ANSI16_TO_BG = new Map(ANSI256_TO_16_IDX.map(x => [x, x + 10]));
|
|
19
|
-
|
|
20
|
-
// Inspired/sourced from: https://github.com/mina86/ansi_colours/blob/master/src/ansi256.rs
|
|
21
|
-
const ANSI256_GRAY_MAPPING = [
|
|
22
|
-
[16, 5],
|
|
23
|
-
[232, 9], [233, 10], [234, 10], [235, 10], [236, 10], [237, 10], [238, 10], [239, 10], [240, 8],
|
|
24
|
-
[59, 5],
|
|
25
|
-
[241, 7], [242, 10], [243, 9], [244, 9],
|
|
26
|
-
[102, 5],
|
|
27
|
-
[245, 6], [246, 10], [247, 10], [248, 9],
|
|
28
|
-
[145, 5],
|
|
29
|
-
[249, 6], [250, 10], [251, 10], [252, 9],
|
|
30
|
-
[188, 5],
|
|
31
|
-
[253, 6], [254, 10], [255, 14],
|
|
32
|
-
[231, 9]
|
|
33
|
-
].flatMap(([v, r]) => new Array<number>(v).fill(r));
|
|
34
|
-
|
|
35
|
-
const STEPS_256 = [0, 95, 135, 175, 215, 255];
|
|
36
|
-
|
|
37
|
-
const ANSI256_TO_RGB: RGB[] = [
|
|
38
|
-
...STD_PRE.flatMap(p => STD_COLORS.map(c => NAMED_COLORS[`${p}${c}`]).map(toRgbArray)),
|
|
39
|
-
...STEPS_256.flatMap(r => STEPS_256.flatMap(g => STEPS_256.map(b => _rgb(r, g, b)))),
|
|
40
|
-
...new Array(24).fill(0).map((_, i) => _rgb(i * 10 + 8)) // Grays
|
|
41
|
-
];
|
|
42
|
-
|
|
43
|
-
// Pulled from: https://github.com/mina86/ansi_colours/blob/master/src/ansi256.rs
|
|
44
|
-
const RED_256_LEVELS = [38, 115, 155, 196, 235];
|
|
45
|
-
const GREEN_256_LEVELS = [38, 115, 155, 196, 235];
|
|
46
|
-
const BLUE_256_LEVELS = [35, 115, 155, 195, 235];
|
|
47
|
-
|
|
48
|
-
export class ColorDefineUtil {
|
|
49
|
-
static CACHE = new Map<number | string, DefinedColor>();
|
|
50
|
-
|
|
51
|
-
static #snapToLevel(levels: number[], val: number): number {
|
|
52
|
-
for (let i = 0; i < levels.length; i += 1) {
|
|
53
|
-
if (val < levels[i]) { return i; }
|
|
54
|
-
}
|
|
55
|
-
return levels.length;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Returns luminance of given sRGB color.
|
|
59
|
-
// Pulled from: https://github.com/mina86/ansi_colours/blob/master/src/ansi256.rs
|
|
60
|
-
// eslint-disable-next-line no-bitwise
|
|
61
|
-
static #luminance = ([r, g, b]: RGB): number => (((3567664 * r + 11998547 * g + 1211005 * b) + (1 << 23)) >> 24) & 255;
|
|
62
|
-
|
|
63
|
-
// Pulled from: https://github.com/mina86/ansi_colours/blob/master/src/ansi256.rs
|
|
64
|
-
static #distance = ([xr, xg, xb]: RGB, [yr, yg, yb]: RGB, sr = xr + yr): number =>
|
|
65
|
-
(1024 + sr) * ((xr - yr) ** 2) + 2048 * ((xg - yg) ** 2) + (1534 - sr) * ((xb - yb) ** 2);
|
|
66
|
-
|
|
67
|
-
static #snapToAnsi256Bands([r, g, b]: RGB): [idx: number, snapped: RGB] {
|
|
68
|
-
const ri = this.#snapToLevel(RED_256_LEVELS, r);
|
|
69
|
-
const gi = this.#snapToLevel(GREEN_256_LEVELS, g);
|
|
70
|
-
const bi = this.#snapToLevel(BLUE_256_LEVELS, b);
|
|
71
|
-
return [(ri * 36 + 16 + gi * 6 + bi), [STEPS_256[ri], STEPS_256[gi], STEPS_256[bi]]];
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Converts [R,G,B] to an ANSI 256 index
|
|
76
|
-
*/
|
|
77
|
-
// Inspired/sourced from: https://github.com/mina86/ansi_colours/blob/master/src/ansi256.rs
|
|
78
|
-
static ansi256FromRgb(rgb: RGB): number {
|
|
79
|
-
const grayIdx = ANSI256_GRAY_MAPPING[this.#luminance(rgb)];
|
|
80
|
-
const grayDist = this.#distance(rgb, ANSI256_TO_RGB[grayIdx]);
|
|
81
|
-
const [snappedIdx, snapped] = this.#snapToAnsi256Bands(rgb);
|
|
82
|
-
return this.#distance(rgb, snapped) < grayDist ? snappedIdx : grayIdx;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Converts [R,G,B] to an ANSI 16 index
|
|
87
|
-
*/
|
|
88
|
-
static ansi16FromRgb(rgb: RGB): number {
|
|
89
|
-
let min = Number.MAX_SAFE_INTEGER;
|
|
90
|
-
let minIdx = -1;
|
|
91
|
-
for (let i = 0; i < 16; i += 1) {
|
|
92
|
-
const dist = this.#distance(rgb, ANSI256_TO_RGB[i]);
|
|
93
|
-
if (dist < min) {
|
|
94
|
-
min = dist;
|
|
95
|
-
minIdx = i;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
return ANSI256_TO_16_IDX[minIdx];
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* ANSI 256 into RGB
|
|
103
|
-
*/
|
|
104
|
-
static rgbFromAnsi256(val: number): RGB {
|
|
105
|
-
return ANSI256_TO_RGB[val];
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Converts [R,G,B] to [H,S,L]
|
|
110
|
-
*/
|
|
111
|
-
static hsl([r, g, b]: RGB): HSL {
|
|
112
|
-
const [rf, gf, bf] = [r / 255, g / 255, b / 255];
|
|
113
|
-
const max = Math.max(rf, gf, bf), min = Math.min(rf, gf, bf);
|
|
114
|
-
let h = 0;
|
|
115
|
-
let s = 0;
|
|
116
|
-
const l = (max + min) / 2;
|
|
117
|
-
|
|
118
|
-
if (max === min) {
|
|
119
|
-
h = s = 0; // achromatic
|
|
120
|
-
} else {
|
|
121
|
-
const d = max - min;
|
|
122
|
-
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
123
|
-
switch (max) {
|
|
124
|
-
case rf: h = (gf - bf) / d + (gf < bf ? 6 : 0); break;
|
|
125
|
-
case gf: h = (bf - rf) / d + 2; break;
|
|
126
|
-
case bf: h = (rf - gf) / d + 4; break;
|
|
127
|
-
}
|
|
128
|
-
h /= 6;
|
|
129
|
-
}
|
|
130
|
-
return [h, s, l];
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Converts input value into [R,G,B] output
|
|
136
|
-
*/
|
|
137
|
-
static toRgb(val: RGBInput): RGB {
|
|
138
|
-
if (typeof val === 'string') {
|
|
139
|
-
if (val.startsWith('#')) {
|
|
140
|
-
const res = val.match(/^#(?<rh>[a-f0-9]{2})(?<gh>[a-f0-9]{2})(?<bh>[a-f0-9]{2})$/i);
|
|
141
|
-
if (res) {
|
|
142
|
-
const { rh, gh, bh } = res.groups!;
|
|
143
|
-
return [parseInt(rh, 16), parseInt(gh, 16), parseInt(bh, 16)];
|
|
144
|
-
}
|
|
145
|
-
} else if (val in NAMED_COLORS) {
|
|
146
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
147
|
-
return this.toRgb(NAMED_COLORS[val as keyof typeof NAMED_COLORS]);
|
|
148
|
-
}
|
|
149
|
-
throw new Error(`Unknown color format: ${val}`);
|
|
150
|
-
} else if (typeof val === 'number') {
|
|
151
|
-
return toRgbArray(val);
|
|
152
|
-
} else {
|
|
153
|
-
return val;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Determine if a color is light or dark
|
|
159
|
-
*/
|
|
160
|
-
static getScheme(color: RGB): TermColorScheme {
|
|
161
|
-
const [r, g, b] = color;
|
|
162
|
-
return (r + g + b) / 3 < 128 ? 'dark' : 'light';
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Define a color and all its parts
|
|
167
|
-
*/
|
|
168
|
-
static defineColor(val: RGBInput): DefinedColor {
|
|
169
|
-
if (!this.CACHE.has(val)) {
|
|
170
|
-
const rgb = this.toRgb(val);
|
|
171
|
-
const idx16 = this.ansi16FromRgb(rgb);
|
|
172
|
-
const idx256 = this.ansi256FromRgb(rgb);
|
|
173
|
-
const hsl = this.hsl(rgb);
|
|
174
|
-
const idx16bg = ANSI16_TO_BG.get(idx16)!;
|
|
175
|
-
const scheme = this.getScheme(rgb);
|
|
176
|
-
this.CACHE.set(val, { rgb, idx16, idx16bg, idx256, hsl, scheme });
|
|
177
|
-
}
|
|
178
|
-
return this.CACHE.get(val)!;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Build ANSI compatible color codes by level
|
|
183
|
-
*/
|
|
184
|
-
static getColorCodes(inp: RGBInput, bg: boolean): [number[], number[]][] {
|
|
185
|
-
const spec = this.defineColor(inp);
|
|
186
|
-
const { idx16, idx16bg, idx256, rgb } = spec;
|
|
187
|
-
const [open, close] = bg ? [48, 49] : [38, 39];
|
|
188
|
-
return [
|
|
189
|
-
[[], []],
|
|
190
|
-
[[bg ? idx16bg : idx16], [close]],
|
|
191
|
-
[[open, 5, idx256], [close]],
|
|
192
|
-
[[open, 2, ...rgb], [close]]
|
|
193
|
-
];
|
|
194
|
-
}
|
|
195
|
-
}
|
package/src/color-output.ts
DELETED
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import tty from 'tty';
|
|
2
|
-
|
|
3
|
-
import { ColorDefineUtil, RGBInput } from './color-define';
|
|
4
|
-
import { ANSICodes } from './codes';
|
|
5
|
-
import { TermColorScheme, TermColorLevel, TermState, RGB } from './types';
|
|
6
|
-
|
|
7
|
-
export type TermStyle =
|
|
8
|
-
{ text?: RGBInput, background?: RGBInput, italic?: boolean, underline?: boolean, inverse?: boolean, blink?: boolean };
|
|
9
|
-
|
|
10
|
-
export type TermStyleInput = TermStyle | RGBInput;
|
|
11
|
-
export type Prim = string | number | boolean | Date | RegExp;
|
|
12
|
-
export type TermColorPaletteInput = Record<string, TermStyleInput | [dark: TermStyleInput, light: TermStyleInput]>;
|
|
13
|
-
export type TermColorFn = (text: Prim) => string;
|
|
14
|
-
export type TermColorPalette<T> = Record<keyof T, TermColorFn>;
|
|
15
|
-
|
|
16
|
-
const COLOR_LEVEL_MAP = { 1: 0, 4: 1, 8: 2, 24: 3 } as const;
|
|
17
|
-
type ColorBits = keyof (typeof COLOR_LEVEL_MAP);
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Utils for colorizing output
|
|
21
|
-
*/
|
|
22
|
-
export class ColorOutputUtil {
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Detect color level from tty information
|
|
26
|
-
*/
|
|
27
|
-
static async readTermColorLevel(stream: tty.WriteStream): Promise<TermColorLevel> {
|
|
28
|
-
const force = process.env.FORCE_COLOR;
|
|
29
|
-
const disable = process.env.NO_COLOR ?? process.env.NODE_DISABLE_COLORS;
|
|
30
|
-
if (force !== undefined) {
|
|
31
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
32
|
-
return Math.max(Math.min(/^\d+$/.test(force) ? parseInt(force, 10) : 1, 3), 0) as TermColorLevel;
|
|
33
|
-
} else if (disable !== undefined && /^(1|true|yes|on)/i.test(disable)) {
|
|
34
|
-
return 0;
|
|
35
|
-
}
|
|
36
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
37
|
-
return stream.isTTY ? COLOR_LEVEL_MAP[stream.getColorDepth() as ColorBits] : 0;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Read foreground/background color if env var is set
|
|
42
|
-
*/
|
|
43
|
-
static async readBackgroundScheme(
|
|
44
|
-
stream: tty.WriteStream,
|
|
45
|
-
query: () => Promise<RGB | undefined> | RGB | undefined,
|
|
46
|
-
env: string | undefined = process.env.COLORFGBG
|
|
47
|
-
): Promise<TermColorScheme | undefined> {
|
|
48
|
-
let color = stream.isTTY ? await query() : undefined;
|
|
49
|
-
if (!color && env) {
|
|
50
|
-
const [, bg] = env.split(';');
|
|
51
|
-
color = ColorDefineUtil.rgbFromAnsi256(+bg);
|
|
52
|
-
}
|
|
53
|
-
if (color) {
|
|
54
|
-
const hex = `#${color.map(x => x.toString(16).padStart(2, '0')).join('')}` as const;
|
|
55
|
-
return ColorDefineUtil.defineColor(hex).scheme;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Get styled levels, 0-3
|
|
61
|
-
*/
|
|
62
|
-
static getStyledLevels(inp: TermStyle | RGBInput): [string, string][] {
|
|
63
|
-
const cfg = (typeof inp !== 'object') ? { text: inp } : inp;
|
|
64
|
-
const levelPairs: [string, string][] = [['', '']];
|
|
65
|
-
const text = cfg.text ? ColorDefineUtil.getColorCodes(cfg.text, false) : undefined;
|
|
66
|
-
const bg = cfg.background ? ColorDefineUtil.getColorCodes(cfg.background, true) : undefined;
|
|
67
|
-
|
|
68
|
-
for (const level of [1, 2, 3]) {
|
|
69
|
-
const prefix: number[] = [];
|
|
70
|
-
const suffix: number[] = [];
|
|
71
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
72
|
-
for (const key of Object.keys(cfg) as (keyof typeof cfg)[]) {
|
|
73
|
-
if (!cfg[key]) {
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
switch (key) {
|
|
77
|
-
case 'inverse': prefix.push(7); suffix.push(27); break;
|
|
78
|
-
case 'underline': prefix.push(4); suffix.push(24); break;
|
|
79
|
-
case 'italic': prefix.push(3); suffix.push(23); break;
|
|
80
|
-
case 'blink': prefix.push(5); suffix.push(25); break;
|
|
81
|
-
case 'text': prefix.push(...text![level][0]); suffix.push(...text![level][1]); break;
|
|
82
|
-
case 'background': prefix.push(...bg![level][0]); suffix.push(...bg![level][1]); break;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
levelPairs[level] = [
|
|
86
|
-
ANSICodes.STYLE(prefix),
|
|
87
|
-
ANSICodes.STYLE(suffix.reverse())
|
|
88
|
-
];
|
|
89
|
-
}
|
|
90
|
-
return levelPairs;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Make a simple primitive colorer
|
|
95
|
-
*/
|
|
96
|
-
static colorer(term: TermState, style: TermStyleInput | [dark: TermStyleInput, light: TermStyleInput]): TermColorFn {
|
|
97
|
-
const schemes = {
|
|
98
|
-
light: this.getStyledLevels(Array.isArray(style) ? style[1] ?? style[0] : style),
|
|
99
|
-
dark: this.getStyledLevels(Array.isArray(style) ? style[0] : style),
|
|
100
|
-
};
|
|
101
|
-
return (v: Prim): string => {
|
|
102
|
-
const [prefix, suffix] = schemes[term.backgroundScheme][term.colorLevel];
|
|
103
|
-
return (v === undefined || v === null) ? '' : `${prefix}${v}${suffix}`;
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Creates a color palette based on input styles
|
|
109
|
-
*/
|
|
110
|
-
static palette<P extends TermColorPaletteInput>(term: TermState, input: P): TermColorPalette<P> {
|
|
111
|
-
// Common color support
|
|
112
|
-
const out: Partial<TermColorPalette<P>> = {};
|
|
113
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
114
|
-
for (const [k, col] of Object.entries(input) as [keyof P, TermStyleInput][]) {
|
|
115
|
-
out[k] = this.colorer(term, col);
|
|
116
|
-
}
|
|
117
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
118
|
-
return out as TermColorPalette<P>;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Convenience method to creates a color template function based on input styles
|
|
123
|
-
*/
|
|
124
|
-
static templateFunction<P extends TermColorPaletteInput>(term: TermState, input: P): (key: keyof P, val: Prim) => string {
|
|
125
|
-
const pal = this.palette(term, input);
|
|
126
|
-
return (key: keyof P, val: Prim) => pal[key](val);
|
|
127
|
-
}
|
|
128
|
-
}
|
package/src/iterable.ts
DELETED
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import timers from 'timers/promises';
|
|
2
|
-
import { DelayedConfig, Indexed } from './types';
|
|
3
|
-
|
|
4
|
-
export type StoppableIterable<T> = { stream: AsyncIterable<T>, stop: () => void };
|
|
5
|
-
|
|
6
|
-
export type MapFn<T, U> = (val: T, i: number) => U | Promise<U>;
|
|
7
|
-
|
|
8
|
-
const isIdx = (x: unknown): x is Indexed => (x !== undefined && x !== null) && typeof x === 'object' && 'idx' in x;
|
|
9
|
-
|
|
10
|
-
export class IterableUtil {
|
|
11
|
-
|
|
12
|
-
static DELAY = ({ initialDelay = 0, cycleDelay = 0 }: DelayedConfig = {}) =>
|
|
13
|
-
<T>(x: T, i: number): Promise<T> => timers.setTimeout(i === 0 ? initialDelay : cycleDelay).then(() => x);
|
|
14
|
-
|
|
15
|
-
static ORDERED = <T extends Indexed>(): (val: T) => boolean => {
|
|
16
|
-
let last = -1;
|
|
17
|
-
return (v: T): boolean => (last = Math.max(v.idx, last)) === v.idx;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
static map<T, U, V, W>(source: AsyncIterable<T>, fn1: MapFn<T, U>, fn2: MapFn<U, V>, fn3: MapFn<V, W>): AsyncIterable<W>;
|
|
21
|
-
static map<T, U, V>(source: AsyncIterable<T>, fn1: MapFn<T, U>, fn2: MapFn<U, V>): AsyncIterable<V>;
|
|
22
|
-
static map<T, U>(source: AsyncIterable<T>, fn: MapFn<T, U>): AsyncIterable<U>;
|
|
23
|
-
static async * map<T>(source: AsyncIterable<T>, ...fns: MapFn<unknown, unknown>[]): AsyncIterable<unknown> {
|
|
24
|
-
let idx = -1;
|
|
25
|
-
for await (const el of source) {
|
|
26
|
-
if (el !== undefined) {
|
|
27
|
-
idx += 1;
|
|
28
|
-
let m = el;
|
|
29
|
-
for (const fn of fns) {
|
|
30
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
31
|
-
m = (await fn(m, idx)) as typeof m;
|
|
32
|
-
}
|
|
33
|
-
yield m;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
static async * filter<T>(source: AsyncIterable<T>, pred: (val: T, i: number) => boolean | Promise<boolean>): AsyncIterable<T> {
|
|
39
|
-
let idx = -1;
|
|
40
|
-
for await (const el of source) {
|
|
41
|
-
if (await pred(el, idx += 1)) {
|
|
42
|
-
yield el;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
static cycle<T>(items: T[]): StoppableIterable<T> {
|
|
48
|
-
let done = false;
|
|
49
|
-
async function* buildStream(): AsyncIterable<T> {
|
|
50
|
-
let i = -1;
|
|
51
|
-
while (!done) {
|
|
52
|
-
yield items[(i += 1) % items.length];
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
return { stream: buildStream(), stop: (): void => { done = true; } };
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
static async drain<T>(source: AsyncIterable<T>): Promise<T[]> {
|
|
59
|
-
const items: T[] = [];
|
|
60
|
-
for await (const ev of source) {
|
|
61
|
-
items[isIdx(ev) ? ev.idx : items.length] = ev;
|
|
62
|
-
}
|
|
63
|
-
return items;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
static simpleQueue(): {
|
|
67
|
-
close: () => Promise<void>;
|
|
68
|
-
add: <T>(item: () => Promise<T>) => Promise<T>;
|
|
69
|
-
running: Promise<void>;
|
|
70
|
-
} {
|
|
71
|
-
let done = false;
|
|
72
|
-
let fire: () => void;
|
|
73
|
-
let next = new Promise<void>(r => fire = r);
|
|
74
|
-
|
|
75
|
-
const queue: Function[] = [];
|
|
76
|
-
async function run(): Promise<void> {
|
|
77
|
-
while (!done) {
|
|
78
|
-
if (!queue.length) {
|
|
79
|
-
await next;
|
|
80
|
-
next = new Promise(r => fire = r);
|
|
81
|
-
}
|
|
82
|
-
await queue.shift()?.();
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
const running = run();
|
|
86
|
-
return {
|
|
87
|
-
running,
|
|
88
|
-
close: (): Promise<void> => {
|
|
89
|
-
done = true;
|
|
90
|
-
fire();
|
|
91
|
-
return running;
|
|
92
|
-
},
|
|
93
|
-
add: <T>(fn: () => Promise<T>): Promise<T> => {
|
|
94
|
-
const prom = new Promise<T>(r => queue.push(() => fn().then(r)));
|
|
95
|
-
fire();
|
|
96
|
-
return prom;
|
|
97
|
-
}
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
}
|