@travetto/terminal 3.0.3 → 3.1.0-rc.1

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 CHANGED
@@ -38,12 +38,12 @@ const tplFn = GlobalTerminal.templateFunction({
38
38
  path: 'teal',
39
39
  success: 'green',
40
40
  failure: 'red',
41
- param: 'yellow',
41
+ param: ['yellow', 'goldenrod'],
42
42
  type: 'cyan',
43
- description: 'white',
44
- title: 'brightWhite',
43
+ description: ['white', 'gray'],
44
+ title: ['brightWhite', 'black'],
45
45
  identifier: 'dodgerBlue',
46
- subtitle: 'lightGray',
46
+ subtitle: ['lightGray', 'darkGray'],
47
47
  subsubtitle: 'darkGray'
48
48
  });
49
49
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/terminal",
3
- "version": "3.0.3",
3
+ "version": "3.1.0-rc.1",
4
4
  "description": "General terminal support",
5
5
  "keywords": [
6
6
  "terminal",
@@ -9,7 +9,7 @@ export type TermStyle =
9
9
 
10
10
  export type TermStyleInput = TermStyle | RGBInput;
11
11
  export type Prim = string | number | boolean | Date | RegExp;
12
- export type TermColorPaletteInput = Record<string, TermStyleInput | [TermStyleInput, TermStyleInput]>;
12
+ export type TermColorPaletteInput = Record<string, TermStyleInput | [dark: TermStyleInput, light: TermStyleInput]>;
13
13
  export type TermColorFn = (text: Prim) => string;
14
14
  export type TermColorPalette<T> = Record<keyof T, TermColorFn>;
15
15
 
@@ -92,7 +92,7 @@ export class ColorOutputUtil {
92
92
  /**
93
93
  * Make a simple primitive colorer
94
94
  */
95
- static colorer(term: TermState, style: TermStyleInput | [light: TermStyleInput, dark: TermStyleInput]): TermColorFn {
95
+ static colorer(term: TermState, style: TermStyleInput | [dark: TermStyleInput, light: TermStyleInput]): TermColorFn {
96
96
  const schemes = {
97
97
  light: this.getStyledLevels(Array.isArray(style) ? style[1] ?? style[0] : style),
98
98
  dark: this.getStyledLevels(Array.isArray(style) ? style[0] : style),
package/src/operation.ts CHANGED
@@ -5,8 +5,18 @@ import { ColorOutputUtil, TermStyleInput } from './color-output';
5
5
 
6
6
  const STD_WAIT_STATES = '⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'.split('');
7
7
 
8
+ /**
9
+ * Standard Terminal Operations
10
+ */
8
11
  export class TerminalOperation {
9
12
 
13
+ static truncateIfNeeded(term: TermState, text: string, suffix = '...'): string {
14
+ if (text.length > term.width) {
15
+ return `${text.substring(0, term.width - suffix.length)}${suffix}`;
16
+ }
17
+ return text;
18
+ }
19
+
10
20
  /**
11
21
  * Allows for writing at top, bottom, or current position while new text is added
12
22
  */
@@ -32,9 +42,23 @@ export class TerminalOperation {
32
42
  }
33
43
  await batch.commit();
34
44
 
45
+ let start = Date.now();
46
+ const minDelay = config.minDelay ?? 0;
47
+
48
+ let line: string = '';
35
49
  for await (const text of source) {
36
- await TerminalWriter.for(term).setPosition(writePos).write(text).clearLine(1).commit(true);
50
+ line = text;
51
+ if ((Date.now() - start) >= minDelay) {
52
+ start = Date.now();
53
+ await TerminalWriter.for(term).setPosition(writePos).write(line).clearLine(1).commit(true);
54
+ line = '';
55
+ }
37
56
  }
57
+
58
+ if (line) {
59
+ await TerminalWriter.for(term).setPosition(writePos).write(line).clearLine(1).commit(true);
60
+ }
61
+
38
62
  if (config.clearOnFinish ?? true) {
39
63
  await TerminalWriter.for(term).setPosition(writePos).clearLine().commit(true);
40
64
  }
@@ -90,7 +114,7 @@ export class TerminalOperation {
90
114
  .replace(/%idx/, idxStr)
91
115
  .replace(/%total/, totalStr)
92
116
  .replace(/%pct/, `${Math.trunc(pct * 100)}`);
93
- const full = ` ${line}`.padEnd(term.width);
117
+ const full = this.truncateIfNeeded(term, ` ${line}`.padEnd(term.width));
94
118
  const mid = Math.trunc(pct * term.width);
95
119
  const [l, r] = [full.substring(0, mid), full.substring(mid)];
96
120
  return `${color(l)}${r}`;
@@ -125,7 +149,7 @@ export class TerminalOperation {
125
149
  if (msg !== undefined) {
126
150
  msg = msg.replace(/\n$/, '');
127
151
  pos = await term.getCursorPosition();
128
- writer = this.streamWaiting(term, msg, { ...cfg, at: pos, clearOnFinish: false });
152
+ writer = this.streamWaiting(term, this.truncateIfNeeded(term, msg), { ...cfg, at: pos, clearOnFinish: false });
129
153
  line = msg;
130
154
  }
131
155
  }
package/src/query.ts CHANGED
@@ -1,4 +1,5 @@
1
- import tty from 'tty';
1
+ import type tty from 'tty';
2
+ import { spawn } from 'child_process';
2
3
 
3
4
  import { ANSICodes } from './codes';
4
5
  import { IterableUtil } from './iterable';
@@ -7,6 +8,31 @@ import { RGB, TermCoord, TermQuery } from './types';
7
8
  const to256 = (x: string): number => Math.trunc(parseInt(x, 16) / (16 ** (x.length - 2)));
8
9
  const COLOR_RESPONSE = /(?<r>][0-9a-f]+)[/](?<g>[0-9a-f]+)[/](?<b>[0-9a-f]+)[/]?(?<a>[0-9a-f]+)?/i;
9
10
 
11
+ // @ts-expect-error
12
+ const queryScript = (function (...bytes) {
13
+ const i = process.stdin;
14
+ i.setRawMode(true);
15
+ i.resume();
16
+ i.once('readable', function () {
17
+ const inp = i.read();
18
+ /* @ts-expect-error */
19
+ process.send(Buffer.isBuffer(inp) ? inp.toString('utf8') : inp);
20
+ i.setRawMode(false);
21
+ });
22
+ process.stdout.write(String.fromCharCode(...bytes));
23
+ });
24
+
25
+ const runQuery = async (input: tty.ReadStream, output: tty.WriteStream, code: string): Promise<Buffer> => {
26
+ const script = queryScript.toString().replaceAll('\'', '"').replaceAll('\n', '');
27
+ const fullScript = `(${script})(${code.split('').map(x => x.charCodeAt(0))})`
28
+ const proc = spawn(process.argv0, ['-e', fullScript], { stdio: [input, output, 2, 'ipc'], detached: true });
29
+ const text = await new Promise<string>((res, rej) => {
30
+ proc.once('message', res);
31
+ proc.on('error', rej);
32
+ });
33
+ return Buffer.from(text, 'utf8');
34
+ };
35
+
10
36
  const ANSIQueries = {
11
37
  /** Parse xterm color response */
12
38
  color: (field: 'background' | 'foreground'): TermQuery<RGB | undefined> => ({
@@ -43,43 +69,15 @@ export class TerminalQuerier {
43
69
  #queue = IterableUtil.simpleQueue();
44
70
  #output: tty.WriteStream;
45
71
  #input: tty.ReadStream;
72
+ #restore?: () => void;
46
73
 
47
74
  constructor(input: tty.ReadStream, output: tty.WriteStream) {
48
75
  this.#input = input;
49
76
  this.#output = output;
50
77
  }
51
78
 
52
- async #readInput(query: string): Promise<Buffer> {
53
- const isRaw = this.#input.isRaw;
54
- const isPaused = this.#input.isPaused();
55
- const data = this.#input.listeners('data');
56
- try {
57
- this.#input.removeAllListeners('data');
58
-
59
- if (isPaused) {
60
- this.#input.resume();
61
- }
62
-
63
- this.#input.setRawMode(true);
64
- // Send data, but do not wait on it
65
- this.#output.write(query);
66
- await new Promise(res => this.#input.once('readable', res));
67
- const val: Buffer | string = this.#input.read();
68
- return typeof val === 'string' ? Buffer.from(val, 'utf8') : val;
69
- } finally {
70
- if (isPaused) {
71
- this.#input.pause();
72
- }
73
- this.#input.setRawMode(isRaw);
74
- for (const fn of data) {
75
- // @ts-ignore
76
- this.#input.on('data', fn);
77
- }
78
- }
79
- }
80
-
81
79
  query<T>(q: TermQuery<T>): Promise<T> {
82
- return this.#queue.add(() => this.#readInput(q.query()).then(q.response));
80
+ return this.#queue.add(() => runQuery(this.#input, this.#output, q.query()).then(q.response));
83
81
  }
84
82
 
85
83
  cursorPosition(): Promise<TermCoord> {
@@ -90,7 +88,8 @@ export class TerminalQuerier {
90
88
  return this.query(ANSIQueries.color('background'));
91
89
  }
92
90
 
93
- close(): Promise<void> {
94
- return this.#queue.close();
91
+ close(): void {
92
+ this.#restore?.();
93
+ this.#queue.close();
95
94
  }
96
95
  }
package/src/terminal.ts CHANGED
@@ -13,6 +13,7 @@ import { ColorOutputUtil, Prim, TermColorFn, TermColorPalette, TermColorPaletteI
13
13
  type TerminalStreamPositionConfig = {
14
14
  position?: TermLinePosition;
15
15
  staticMessage?: string;
16
+ minDelay?: number;
16
17
  };
17
18
 
18
19
  type TerminalProgressConfig = TerminalStreamPositionConfig & {
@@ -98,12 +99,11 @@ export class Terminal implements TermState {
98
99
  return this.#init;
99
100
  }
100
101
 
101
- async reset(): Promise<void> {
102
- await this.#query.close();
102
+ reset(): void {
103
+ this.#query.close();
103
104
  if (this.interactive) {
104
- await this.writer().reset().commit();
105
+ this.#output.write(this.writer().resetCommands());
105
106
  }
106
- return;
107
107
  }
108
108
 
109
109
  getCursorPosition(): Promise<TermCoord> {
package/src/types.ts CHANGED
@@ -13,7 +13,7 @@ export type TerminalTableEvent = { idx: number, text: string, done?: boolean };
13
13
  export type TerminalTableConfig = { header?: string[], forceNonInteractiveOrder?: boolean };
14
14
  export type TerminalProgressEvent = { idx: number, total?: number, text?: string };
15
15
  export type TerminalProgressRender = (ev: TerminalProgressEvent) => string;
16
- export type TerminalStreamingConfig = { position?: TermLinePosition, clearOnFinish?: boolean, at?: TermCoord };
16
+ export type TerminalStreamingConfig = { position?: TermLinePosition, clearOnFinish?: boolean, at?: TermCoord, minDelay?: number };
17
17
  export type TerminalWaitingConfig = { end?: boolean, committedPrefix?: string } & TerminalStreamingConfig & DelayedConfig;
18
18
 
19
19
  export type TermColorLevel = 0 | 1 | 2 | 3;
package/src/writer.ts CHANGED
@@ -137,10 +137,16 @@ export class TerminalWriter {
137
137
 
138
138
  /** Reset */
139
139
  reset(): this {
140
- return this
141
- .write(ANSICodes.POSITION_SAVE())
142
- .write(ANSICodes.SHOW_CURSOR())
143
- .write(ANSICodes.SCROLL_RANGE_CLEAR())
144
- .write(ANSICodes.POSITION_RESTORE());
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('');
145
151
  }
146
152
  }