@travetto/terminal 8.0.0-alpha.9 → 8.0.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
@@ -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. The functionality can be broken down into:
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. The standards are somewhat inconsistent and detection can be a tricky process. For terminals, [Node](https://nodejs.org) supports 4 different levels of coloring:
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. Can represent any color needed
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. Support for this is widespread, but when it fails, it will gracefully assume a dark background.
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. 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.
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. This mainly revolves around indicating progress while a program is executing. The module provides support for:
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. 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.
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. 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.
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
@@ -1,5 +1,6 @@
1
- import type { } from './src/trv.d.ts';
1
+ import type {} from './src/trv.d.ts';
2
+
2
3
  export * from './src/style.ts';
3
4
  export * from './src/terminal.ts';
5
+ export * from './src/util.ts';
4
6
  export * from './src/writer.ts';
5
- export * from './src/util.ts';
package/package.json CHANGED
@@ -1,38 +1,38 @@
1
1
  {
2
2
  "name": "@travetto/terminal",
3
- "version": "8.0.0-alpha.9",
4
- "type": "module",
3
+ "version": "8.0.1",
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
- "email": "travetto.framework@gmail.com",
16
- "name": "Travetto Framework"
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
- "repository": {
24
- "url": "git+https://github.com/travetto/travetto.git",
25
- "directory": "module/terminal"
28
+ "publishConfig": {
29
+ "access": "public"
26
30
  },
27
31
  "dependencies": {
28
- "@travetto/runtime": "^8.0.0-alpha.9",
29
- "chalk": "^5.6.2"
32
+ "@travetto/runtime": "^8.0.1",
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 = Color | { text: Color, background?: Color, inverse?: boolean, bold?: boolean, italic?: boolean, underline?: boolean };
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> = (values: TemplateStringsArray, ...keys: (Partial<Record<T, TemplatePrim>> | string)[]) => 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], [128, 0, 0], [0, 128, 0], [128, 128, 0],
15
- [0, 0, 128], [128, 0, 128], [0, 128, 128], [192, 192, 192],
16
- [128, 128, 128], [255, 0, 0], [0, 255, 0], [255, 255, 0],
17
- [0, 0, 255], [255, 0, 255], [0, 255, 255], [255, 255, 255]
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, dark: boolean } = { key: '', dark: true };
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': style = style.hex(input[key]!); break;
65
- case 'background': style = style.bgHex(input[key]!); break;
66
- default: style = (input[key] ? style[key] : style); break;
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, outputStreamToMain?: boolean };
8
- type Coord = { x: number, y: 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.setPosition(position).write(STD_WAIT_STATES[i++ % STD_WAIT_STATES.length]).commit(true);
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, height?: 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 { return this.#output; }
44
- get width(): number { return this.#width; }
45
- get height(): number { return this.#height; }
46
- get writer(): TerminalWriter { return this.#writer; }
47
- get interactive(): boolean { return this.#interactive; }
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(Util.mapAsyncIterable(source, line => `%WAIT% ${line}`), { outputStreamToMain: true });
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.hideCursor()
77
- .storePosition().scrollRange({ end: -2 }).restorePosition()
78
- .changePosition({ y: -1 }).write('\n')
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 && (Date.now() - start) >= minDelay) {
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, text: string, done?: boolean }>): Promise<void> {
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.changePosition({ y: max + 1 }).writeLine('\n').reset().commit();
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
- * Terminal operation mode, false means simple output
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, idx: number, value: T };
5
- type ProgressStyle = { complete: TermStyleFn, incomplete?: 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, incomplete?: TermStyleInput } | (() => ProgressStyle);
41
+ style?: { complete: TermStyleInput; failed?: TermStyleInput; incomplete?: TermStyleInput } | (() => ProgressStyle);
17
42
  }
18
43
  ): (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
- } : undefined;
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 ? '%idx/%total' : '%idx');
29
- const progress = event.total === undefined ? 0 : (event.idx / event.total);
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> = { 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])} `;
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
- return `${config?.withWaiting ? `${WAIT_TOKEN} ` : ''}${complete(left)}${incomplete?.(right) ?? right}`;
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, height: number, width: number };
6
- type TermCoord = { x: number, y: 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 => y !== undefined ?
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 > (this.#term.width - offset)) {
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: return this.write(Codes.ERASE_LINE_ALL);
111
- case 1: return this.write(Codes.ERASE_LINE_RIGHT);
112
- case -1: return this.write(Codes.ERASE_LINE_LEFT);
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, end?: number }): this {
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
+ }