@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/src/writer.ts CHANGED
@@ -1,28 +1,50 @@
1
- import { ANSICodes } from './codes';
2
- import { TermCoord, TermState } from './types';
3
-
4
- const boundIndex = (v: number, size: number): number => {
5
- if (v < 0) {
6
- v = size + v;
7
- }
8
- return Math.max(Math.min(v, size - 1), 0);
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
- static for(term: TermState): TerminalWriter {
17
- return new TerminalWriter(term);
38
+ constructor(state: State) {
39
+ this.#term = state;
18
40
  }
19
41
 
20
- #queue: (string | number)[] = [];
21
- #term: TermState;
22
- #restoreOnCommit = false;
23
-
24
- constructor(term: TermState) {
25
- this.#term = 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.#queue.filter(x => x !== undefined);
36
- this.#queue = [];
57
+ const q = this.#buffer.filter(x => x !== undefined);
58
+ this.#buffer = [];
37
59
  if (q.length && restorePosition) {
38
- q.unshift(ANSICodes.POSITION_SAVE());
39
- q.push(ANSICodes.POSITION_RESTORE());
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.#queue.push(...text);
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(ANSICodes.POSITION_SAVE());
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(ANSICodes.POSITION_RESTORE());
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
- return this.write(ANSICodes.ERASE_LINE(dir === 0 ? 2 : (dir === 1 ? 0 : 1)));
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
- if (y !== undefined) {
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
- if (x) {
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', `${ANSICodes.ERASE_LINE(0)}\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(ANSICodes.SHOW_CURSOR());
124
+ return this.write(Codes.SHOW_CURSOR);
114
125
  }
115
126
 
116
127
  /** Hide cursor */
117
128
  hideCursor(): this {
118
- return this.write(ANSICodes.HIDE_CURSOR());
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
- start = boundIndex(start ?? 0, this.#term.output.rows);
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(ANSICodes.SCROLL_RANGE_CLEAR());
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
- reset(): this {
140
- return this.write(this.resetCommands());
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, '');
@@ -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
- }
@@ -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
- }