console-toolkit 1.2.8 → 1.2.10
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 +70 -25
- package/package.json +26 -6
- package/src/alphanumeric/arrows.d.ts +48 -0
- package/src/alphanumeric/arrows.js +23 -0
- package/src/alphanumeric/fractions.d.ts +65 -0
- package/src/alphanumeric/fractions.js +49 -0
- package/src/alphanumeric/number-formatters.d.ts +91 -0
- package/src/alphanumeric/number-formatters.js +45 -1
- package/src/alphanumeric/roman.d.ts +15 -0
- package/src/alphanumeric/roman.js +12 -0
- package/src/alphanumeric/unicode-cultural-numbers.d.ts +65 -0
- package/src/alphanumeric/unicode-cultural-numbers.js +1 -0
- package/src/alphanumeric/unicode-letters.d.ts +32 -0
- package/src/alphanumeric/unicode-letters.js +8 -0
- package/src/alphanumeric/unicode-numbers.d.ts +44 -0
- package/src/alphanumeric/unicode-numbers.js +21 -0
- package/src/alphanumeric/utils.d.ts +45 -0
- package/src/alphanumeric/utils.js +26 -0
- package/src/ansi/csi.d.ts +141 -0
- package/src/ansi/csi.js +51 -2
- package/src/ansi/index.d.ts +26 -0
- package/src/ansi/sgr-constants.d.ts +173 -0
- package/src/ansi/sgr-state.d.ts +91 -0
- package/src/ansi/sgr-state.js +45 -0
- package/src/ansi/sgr.d.ts +587 -0
- package/src/ansi/sgr.js +426 -6
- package/src/box.d.ts +160 -0
- package/src/box.js +113 -12
- package/src/charts/bars/block-frac-grouped.d.ts +12 -0
- package/src/charts/bars/block-frac-grouped.js +6 -0
- package/src/charts/bars/block-frac.d.ts +34 -0
- package/src/charts/bars/block-frac.js +13 -0
- package/src/charts/bars/block-grouped.d.ts +12 -0
- package/src/charts/bars/block-grouped.js +6 -0
- package/src/charts/bars/block.d.ts +43 -0
- package/src/charts/bars/block.js +13 -0
- package/src/charts/bars/draw-grouped.d.ts +41 -0
- package/src/charts/bars/draw-grouped.js +4 -0
- package/src/charts/bars/draw-stacked.d.ts +47 -0
- package/src/charts/bars/draw-stacked.js +4 -0
- package/src/charts/bars/frac-grouped.d.ts +32 -0
- package/src/charts/bars/frac-grouped.js +13 -0
- package/src/charts/bars/plain-grouped.d.ts +12 -0
- package/src/charts/bars/plain-grouped.js +6 -0
- package/src/charts/bars/plain.d.ts +75 -0
- package/src/charts/bars/plain.js +27 -0
- package/src/charts/columns/block-frac-grouped.d.ts +12 -0
- package/src/charts/columns/block-frac-grouped.js +6 -0
- package/src/charts/columns/block-frac.d.ts +39 -0
- package/src/charts/columns/block-frac.js +13 -0
- package/src/charts/columns/block-grouped.d.ts +12 -0
- package/src/charts/columns/block-grouped.js +6 -0
- package/src/charts/columns/block.d.ts +43 -0
- package/src/charts/columns/block.js +13 -0
- package/src/charts/columns/draw-grouped.d.ts +41 -0
- package/src/charts/columns/draw-grouped.js +4 -0
- package/src/charts/columns/draw-stacked.d.ts +39 -0
- package/src/charts/columns/draw-stacked.js +4 -0
- package/src/charts/columns/frac-grouped.d.ts +37 -0
- package/src/charts/columns/frac-grouped.js +13 -0
- package/src/charts/columns/plain-grouped.d.ts +12 -0
- package/src/charts/columns/plain-grouped.js +6 -0
- package/src/charts/columns/plain.d.ts +32 -0
- package/src/charts/columns/plain.js +13 -0
- package/src/charts/themes/default.d.ts +6 -0
- package/src/charts/themes/default.js +1 -0
- package/src/charts/themes/rainbow-reversed.d.ts +6 -0
- package/src/charts/themes/rainbow-reversed.js +2 -1
- package/src/charts/themes/rainbow.d.ts +6 -0
- package/src/charts/themes/rainbow.js +1 -0
- package/src/charts/utils.d.ts +79 -0
- package/src/charts/utils.js +32 -4
- package/src/draw-block-frac.d.ts +16 -0
- package/src/draw-block-frac.js +14 -0
- package/src/draw-block.d.ts +53 -0
- package/src/draw-block.js +25 -1
- package/src/meta.d.ts +84 -0
- package/src/meta.js +64 -0
- package/src/output/show.d.ts +55 -0
- package/src/output/show.js +28 -0
- package/src/output/updater.d.ts +114 -0
- package/src/output/updater.js +58 -4
- package/src/output/writer.d.ts +87 -0
- package/src/output/writer.js +57 -5
- package/src/panel.d.ts +402 -0
- package/src/panel.js +219 -5
- package/src/plot/bitmap.d.ts +80 -0
- package/src/plot/bitmap.js +33 -4
- package/src/plot/draw-line.d.ts +13 -0
- package/src/plot/draw-line.js +8 -0
- package/src/plot/draw-rect.d.ts +13 -0
- package/src/plot/draw-rect.js +38 -30
- package/src/plot/index.d.ts +39 -0
- package/src/plot/index.js +22 -0
- package/src/plot/to-quads.d.ts +10 -0
- package/src/plot/to-quads.js +5 -0
- package/src/spinner/index.d.ts +4 -0
- package/src/spinner/index.js +0 -2
- package/src/spinner/spin.d.ts +13 -0
- package/src/spinner/spin.js +13 -2
- package/src/spinner/spinner.d.ts +69 -0
- package/src/spinner/spinner.js +30 -2
- package/src/spinner/spinners.d.ts +34 -0
- package/src/spinner/spinners.js +23 -9
- package/src/strings/clip.d.ts +21 -0
- package/src/strings/clip.js +10 -0
- package/src/strings/parse.d.ts +23 -0
- package/src/strings/parse.js +7 -0
- package/src/strings/split.d.ts +38 -0
- package/src/strings/split.js +15 -0
- package/src/strings.d.ts +44 -0
- package/src/strings.js +34 -4
- package/src/style.d.ts +462 -0
- package/src/style.js +58 -4
- package/src/symbols.d.ts +167 -0
- package/src/symbols.js +91 -7
- package/src/table/draw-borders.d.ts +38 -0
- package/src/table/draw-borders.js +10 -2
- package/src/table/index.d.ts +8 -0
- package/src/table/index.js +1 -0
- package/src/table/table.d.ts +234 -0
- package/src/table/table.js +59 -1
- package/src/themes/blocks/unicode-half.d.ts +6 -0
- package/src/themes/blocks/unicode-half.js +1 -0
- package/src/themes/blocks/unicode-thin.d.ts +6 -0
- package/src/themes/blocks/unicode-thin.js +1 -0
- package/src/themes/lines/ascii-compact.d.ts +6 -0
- package/src/themes/lines/ascii-compact.js +1 -0
- package/src/themes/lines/ascii-dots.d.ts +6 -0
- package/src/themes/lines/ascii-dots.js +1 -0
- package/src/themes/lines/ascii-girder.d.ts +6 -0
- package/src/themes/lines/ascii-girder.js +1 -0
- package/src/themes/lines/ascii-github.d.ts +6 -0
- package/src/themes/lines/ascii-github.js +1 -0
- package/src/themes/lines/ascii-reddit.d.ts +6 -0
- package/src/themes/lines/ascii-reddit.js +1 -0
- package/src/themes/lines/ascii-rounded.d.ts +6 -0
- package/src/themes/lines/ascii-rounded.js +1 -0
- package/src/themes/lines/ascii.d.ts +6 -0
- package/src/themes/lines/ascii.js +1 -0
- package/src/themes/lines/unicode-bold.d.ts +6 -0
- package/src/themes/lines/unicode-bold.js +1 -0
- package/src/themes/lines/unicode-rounded.d.ts +6 -0
- package/src/themes/lines/unicode-rounded.js +1 -0
- package/src/themes/lines/unicode.d.ts +6 -0
- package/src/themes/lines/unicode.js +1 -0
- package/src/themes/utils.d.ts +33 -0
- package/src/themes/utils.js +7 -0
- package/src/turtle/draw-line-art.d.ts +19 -0
- package/src/turtle/draw-line-art.js +7 -0
- package/src/turtle/draw-unicode.d.ts +19 -0
- package/src/turtle/draw-unicode.js +8 -0
- package/src/turtle/index.d.ts +21 -0
- package/src/turtle/index.js +8 -0
- package/src/turtle/turtle.d.ts +269 -0
- package/src/turtle/turtle.js +124 -4
package/src/output/show.js
CHANGED
|
@@ -2,6 +2,12 @@ import process from 'node:process';
|
|
|
2
2
|
import {matchCsiNoGroups} from '../strings.js';
|
|
3
3
|
import Box from '../box.js';
|
|
4
4
|
|
|
5
|
+
/** Logs a text container to the console via `console.log()`. Strips ANSI codes if colorDepth < 4.
|
|
6
|
+
* @param {import('../strings.js').StringsInput} s - Input convertible to a Box.
|
|
7
|
+
* @param {object} [options] - Options.
|
|
8
|
+
* @param {string} [options.endOfLineCommand='\x1B[m'] - ANSI command appended to each line.
|
|
9
|
+
* @param {number} [options.colorDepth=24] - Color depth (1 = no color, 4/8/24 = color).
|
|
10
|
+
*/
|
|
5
11
|
export const log = (s, {endOfLineCommand = '\x1B[m', colorDepth = 24} = {}) => {
|
|
6
12
|
s = Box.make(s);
|
|
7
13
|
if (colorDepth < 4) {
|
|
@@ -12,6 +18,13 @@ export const log = (s, {endOfLineCommand = '\x1B[m', colorDepth = 24} = {}) => {
|
|
|
12
18
|
s.box.forEach(row => console.log(row + endOfLineCommand));
|
|
13
19
|
};
|
|
14
20
|
|
|
21
|
+
/** Writes a text container to a stream. Strips ANSI codes if colorDepth < 4.
|
|
22
|
+
* @param {import('../strings.js').StringsInput} s - Input convertible to a Box.
|
|
23
|
+
* @param {object} [options] - Options.
|
|
24
|
+
* @param {import('node:stream').Writable} [options.stream=process.stdout] - The output stream.
|
|
25
|
+
* @param {string} [options.endOfLineCommand='\x1B[m'] - ANSI command appended to each line.
|
|
26
|
+
* @param {number} [options.colorDepth] - Color depth. Auto-detected from stream if not specified.
|
|
27
|
+
*/
|
|
15
28
|
export const out = (s, {stream = process.stdout, endOfLineCommand = '\x1B[m', colorDepth} = {}) => {
|
|
16
29
|
s = Box.make(s);
|
|
17
30
|
if (typeof colorDepth != 'number' || isNaN(colorDepth)) colorDepth = stream.isTTY ? stream.getColorDepth() : 1;
|
|
@@ -23,17 +36,32 @@ export const out = (s, {stream = process.stdout, endOfLineCommand = '\x1B[m', co
|
|
|
23
36
|
s.box.forEach(row => stream.write(row + endOfLineCommand + '\n'));
|
|
24
37
|
};
|
|
25
38
|
|
|
39
|
+
/** Convenience wrapper around a writable stream for formatted output.
|
|
40
|
+
* Auto-detects color depth from the stream.
|
|
41
|
+
*/
|
|
26
42
|
export class Out {
|
|
43
|
+
/**
|
|
44
|
+
* @param {import('node:stream').Writable} stream - The writable stream to wrap.
|
|
45
|
+
*/
|
|
27
46
|
constructor(stream) {
|
|
28
47
|
this.stream = stream;
|
|
29
48
|
this.colorDepth = stream.isTTY ? stream.getColorDepth() : 1;
|
|
30
49
|
}
|
|
50
|
+
/** Writes a text container to the stream.
|
|
51
|
+
* @param {import('../strings.js').StringsInput} s - Input convertible to a Box.
|
|
52
|
+
* @param {object} [options] - Options.
|
|
53
|
+
* @param {string} [options.endOfLineCommand='\x1B[m'] - ANSI command appended to each line.
|
|
54
|
+
* @param {number} [options.colorDepth] - Color depth override.
|
|
55
|
+
*/
|
|
31
56
|
out(s, {endOfLineCommand = '\x1B[m', colorDepth} = {}) {
|
|
32
57
|
if (typeof colorDepth != 'number' || isNaN(colorDepth)) colorDepth = this.colorDepth;
|
|
33
58
|
return out(s, {stream: this.stream, endOfLineCommand, colorDepth});
|
|
34
59
|
}
|
|
35
60
|
}
|
|
36
61
|
|
|
62
|
+
/** Logs a string with control characters visualized as hex escape sequences.
|
|
63
|
+
* @param {string} string - The string to debug.
|
|
64
|
+
*/
|
|
37
65
|
export const debug = string =>
|
|
38
66
|
console.log(
|
|
39
67
|
string.replace(/[\x00-\x1F]/g, m => '\\x' + m[0].charCodeAt(0).toString(16).padStart(2, '0').toUpperCase())
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import Writer from './writer.js';
|
|
2
|
+
import {StringsInput} from '../strings.js';
|
|
3
|
+
|
|
4
|
+
/** Options for `Updater` constructor. */
|
|
5
|
+
export interface UpdaterOptions {
|
|
6
|
+
/** String written before the first frame. */
|
|
7
|
+
prologue?: string;
|
|
8
|
+
/** String written after the final frame. */
|
|
9
|
+
epilogue?: string;
|
|
10
|
+
/** String written before each frame. */
|
|
11
|
+
beforeFrame?: string;
|
|
12
|
+
/** String written after each frame. */
|
|
13
|
+
afterFrame?: string;
|
|
14
|
+
/** String prepended before each line. */
|
|
15
|
+
beforeLine?: string;
|
|
16
|
+
/** String appended after each line. */
|
|
17
|
+
afterLine?: string;
|
|
18
|
+
/** If true, omit the trailing newline on the last line. */
|
|
19
|
+
noLastNewLine?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** An object that can provide frames for the Updater. */
|
|
23
|
+
export interface UpdaterTarget {
|
|
24
|
+
/** Current state string. */
|
|
25
|
+
state?: string;
|
|
26
|
+
/** Returns the current frame content.
|
|
27
|
+
* @returns Frame content (Box, string, or string array).
|
|
28
|
+
*/
|
|
29
|
+
getFrame(...args: unknown[]): StringsInput;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Manages auto-refreshing console output for spinners, progress bars, etc. */
|
|
33
|
+
export class Updater {
|
|
34
|
+
/** The updater function or target object. */
|
|
35
|
+
updater: ((state: string, ...args: unknown[]) => StringsInput) | UpdaterTarget;
|
|
36
|
+
/** The Writer instance used for output. */
|
|
37
|
+
writer: Writer;
|
|
38
|
+
/** String written before the first frame. */
|
|
39
|
+
prologue: string;
|
|
40
|
+
/** String written after the final frame. */
|
|
41
|
+
epilogue: string;
|
|
42
|
+
/** String written before each frame. */
|
|
43
|
+
beforeFrame: string;
|
|
44
|
+
/** String written after each frame. */
|
|
45
|
+
afterFrame: string;
|
|
46
|
+
/** String prepended before each line. */
|
|
47
|
+
beforeLine: string;
|
|
48
|
+
/** String appended after each line. */
|
|
49
|
+
afterLine: string;
|
|
50
|
+
/** If true, omit the trailing newline on the last line. */
|
|
51
|
+
noLastNewLine: boolean | undefined;
|
|
52
|
+
/** Height of the last written frame in rows. */
|
|
53
|
+
lastHeight: number;
|
|
54
|
+
/** Whether the updater has been finalized. */
|
|
55
|
+
isDone: boolean;
|
|
56
|
+
/** Whether this is the first frame. */
|
|
57
|
+
first: boolean;
|
|
58
|
+
/** Handle for the auto-refresh interval, or null. */
|
|
59
|
+
intervalHandle: ReturnType<typeof setInterval> | null;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @param updater - A function or UpdaterTarget that provides frames.
|
|
63
|
+
* @param options - Updater options.
|
|
64
|
+
* @param writer - Writer instance (default: new Writer).
|
|
65
|
+
*/
|
|
66
|
+
constructor(
|
|
67
|
+
updater: ((state: string, ...args: unknown[]) => StringsInput) | UpdaterTarget,
|
|
68
|
+
options?: UpdaterOptions,
|
|
69
|
+
writer?: Writer
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
/** Whether auto-refreshing is active. */
|
|
73
|
+
readonly isRefreshing: boolean;
|
|
74
|
+
|
|
75
|
+
/** Starts auto-refreshing at the given interval.
|
|
76
|
+
* @param ms - Refresh interval in milliseconds (default: 100).
|
|
77
|
+
* @returns This Updater.
|
|
78
|
+
*/
|
|
79
|
+
startRefreshing(ms?: number): this;
|
|
80
|
+
/** Stops auto-refreshing.
|
|
81
|
+
* @returns This Updater.
|
|
82
|
+
*/
|
|
83
|
+
stopRefreshing(): this;
|
|
84
|
+
/** Resets the updater state.
|
|
85
|
+
* @returns This Updater.
|
|
86
|
+
*/
|
|
87
|
+
reset(): this;
|
|
88
|
+
|
|
89
|
+
/** Gets the current frame from the updater.
|
|
90
|
+
* @param state - State string.
|
|
91
|
+
* @returns Frame content.
|
|
92
|
+
*/
|
|
93
|
+
getFrame(state: string, ...args: unknown[]): StringsInput;
|
|
94
|
+
/** Writes a frame to the stream.
|
|
95
|
+
* @param state - State string.
|
|
96
|
+
* @returns A promise that resolves when the frame is written.
|
|
97
|
+
*/
|
|
98
|
+
writeFrame(state: string, ...args: unknown[]): Promise<void>;
|
|
99
|
+
/** Finishes updating: writes the epilogue and stops refreshing.
|
|
100
|
+
* @returns A promise that resolves when done.
|
|
101
|
+
*/
|
|
102
|
+
done(): Promise<void>;
|
|
103
|
+
/** Updates with a new state and writes the frame.
|
|
104
|
+
* @param state - State string.
|
|
105
|
+
* @returns A promise that resolves when the update is written.
|
|
106
|
+
*/
|
|
107
|
+
update(state?: string, ...args: unknown[]): Promise<void>;
|
|
108
|
+
/** Writes the final frame with state 'finished' and calls `done()`.
|
|
109
|
+
* @returns A promise that resolves when done.
|
|
110
|
+
*/
|
|
111
|
+
final(...args: unknown[]): Promise<void>;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export default Updater;
|
package/src/output/updater.js
CHANGED
|
@@ -1,13 +1,30 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
1
|
import Writer from './writer.js';
|
|
4
2
|
import {toStrings} from '../strings.js';
|
|
5
3
|
import {cursorUp, setCommands} from '../ansi/csi.js';
|
|
6
4
|
|
|
7
5
|
const RESET = setCommands([]);
|
|
8
6
|
|
|
7
|
+
/** Manages continuously updating console output (spinners, progress bars, etc.).
|
|
8
|
+
* Handles refreshing frames, prologue/epilogue sequences, and interacts with a Writer instance.
|
|
9
|
+
*/
|
|
9
10
|
export class Updater {
|
|
10
|
-
|
|
11
|
+
/**
|
|
12
|
+
* @param {((state: string, ...args: unknown[]) => import('../strings.js').StringsInput)|{state?: string, getFrame: (...args: unknown[]) => import('../strings.js').StringsInput}} updater - A function `(state, ...args) => frame` or an object with `getFrame()`.
|
|
13
|
+
* @param {object} [options] - Options.
|
|
14
|
+
* @param {string} [options.prologue] - String written before the first frame.
|
|
15
|
+
* @param {string} [options.epilogue] - String written after the last frame.
|
|
16
|
+
* @param {string} [options.beforeFrame] - String written before each frame.
|
|
17
|
+
* @param {string} [options.afterFrame] - String written after each frame.
|
|
18
|
+
* @param {string} [options.beforeLine] - String prepended to each line.
|
|
19
|
+
* @param {string} [options.afterLine] - String appended to each line.
|
|
20
|
+
* @param {boolean} [options.noLastNewLine] - If true, omit the trailing newline of each frame.
|
|
21
|
+
* @param {import('./writer.js').default} [writer=new Writer()] - The Writer instance to use.
|
|
22
|
+
*/
|
|
23
|
+
constructor(
|
|
24
|
+
updater,
|
|
25
|
+
{prologue, epilogue, beforeFrame, afterFrame, beforeLine, afterLine, noLastNewLine} = {},
|
|
26
|
+
writer = new Writer()
|
|
27
|
+
) {
|
|
11
28
|
this.updater = updater;
|
|
12
29
|
this.writer = writer;
|
|
13
30
|
this.prologue = prologue || RESET;
|
|
@@ -23,16 +40,24 @@ export class Updater {
|
|
|
23
40
|
this.intervalHandle = null;
|
|
24
41
|
}
|
|
25
42
|
|
|
43
|
+
/** Whether the updater is currently auto-refreshing. */
|
|
26
44
|
get isRefreshing() {
|
|
27
45
|
return this.intervalHandle !== null;
|
|
28
46
|
}
|
|
29
47
|
|
|
48
|
+
/** Starts auto-refreshing at the given interval.
|
|
49
|
+
* @param {number} [ms=100] - Refresh interval in milliseconds.
|
|
50
|
+
* @returns {this}
|
|
51
|
+
*/
|
|
30
52
|
startRefreshing(ms = 100) {
|
|
31
53
|
if (this.intervalHandle || this.isDone || !this.writer.isTTY) return this;
|
|
32
54
|
this.intervalHandle = setInterval(this.update.bind(this), ms);
|
|
33
55
|
return this;
|
|
34
56
|
}
|
|
35
57
|
|
|
58
|
+
/** Stops auto-refreshing.
|
|
59
|
+
* @returns {this}
|
|
60
|
+
*/
|
|
36
61
|
stopRefreshing() {
|
|
37
62
|
if (!this.intervalHandle) return this;
|
|
38
63
|
clearInterval(this.intervalHandle);
|
|
@@ -40,6 +65,9 @@ export class Updater {
|
|
|
40
65
|
return this;
|
|
41
66
|
}
|
|
42
67
|
|
|
68
|
+
/** Resets the updater state, stopping any refresh and clearing the done flag.
|
|
69
|
+
* @returns {this}
|
|
70
|
+
*/
|
|
43
71
|
reset() {
|
|
44
72
|
this.stopRefreshing();
|
|
45
73
|
this.isDone = false;
|
|
@@ -47,6 +75,11 @@ export class Updater {
|
|
|
47
75
|
return this;
|
|
48
76
|
}
|
|
49
77
|
|
|
78
|
+
/** Gets a frame from the updater function or object.
|
|
79
|
+
* @param {string} state - The current state ('active', 'paused', 'finished', etc.).
|
|
80
|
+
* @param {...unknown} args - Additional arguments.
|
|
81
|
+
* @returns {import('../strings.js').StringsInput} The frame content.
|
|
82
|
+
*/
|
|
50
83
|
getFrame(state, ...args) {
|
|
51
84
|
if (typeof this.updater == 'function') return this.updater(state, ...args);
|
|
52
85
|
if (typeof this.updater?.getFrame == 'function') {
|
|
@@ -56,6 +89,11 @@ export class Updater {
|
|
|
56
89
|
throw new TypeError('Updater must be a function or implement getFrame()');
|
|
57
90
|
}
|
|
58
91
|
|
|
92
|
+
/** Writes a single frame to the output, handling cursor repositioning.
|
|
93
|
+
* @param {string} state - The current state.
|
|
94
|
+
* @param {...unknown} args - Additional arguments passed to `getFrame()`.
|
|
95
|
+
* @returns {Promise<void>}
|
|
96
|
+
*/
|
|
59
97
|
async writeFrame(state, ...args) {
|
|
60
98
|
if (this.first) {
|
|
61
99
|
this.prologue && (await this.writer.writeString(this.prologue));
|
|
@@ -70,10 +108,17 @@ export class Updater {
|
|
|
70
108
|
this.lastHeight = frame.length;
|
|
71
109
|
if (this.noLastNewLine) --this.lastHeight;
|
|
72
110
|
|
|
73
|
-
await this.writer.write(frame, {
|
|
111
|
+
await this.writer.write(frame, {
|
|
112
|
+
noLastNewLine: this.noLastNewLine,
|
|
113
|
+
beforeLine: this.beforeLine,
|
|
114
|
+
afterLine: this.afterLine
|
|
115
|
+
});
|
|
74
116
|
this.afterFrame && (await this.writer.writeString(this.afterFrame));
|
|
75
117
|
}
|
|
76
118
|
|
|
119
|
+
/** Marks the updater as done, stops refreshing, and writes the epilogue.
|
|
120
|
+
* @returns {Promise<void>}
|
|
121
|
+
*/
|
|
77
122
|
async done() {
|
|
78
123
|
if (this.isDone) return;
|
|
79
124
|
this.isDone = true;
|
|
@@ -81,11 +126,20 @@ export class Updater {
|
|
|
81
126
|
this.epilogue && (await this.writer.writeString(this.epilogue));
|
|
82
127
|
}
|
|
83
128
|
|
|
129
|
+
/** Updates the display with a new frame.
|
|
130
|
+
* @param {string} [state='active'] - The current state.
|
|
131
|
+
* @param {...unknown} args - Additional arguments passed to `getFrame()`.
|
|
132
|
+
* @returns {Promise<void>}
|
|
133
|
+
*/
|
|
84
134
|
async update(state = 'active', ...args) {
|
|
85
135
|
if (this.isDone || !this.writer.isTTY) return;
|
|
86
136
|
await this.writeFrame(state, ...args);
|
|
87
137
|
}
|
|
88
138
|
|
|
139
|
+
/** Writes the final frame with state 'finished' and calls `done()`.
|
|
140
|
+
* @param {...unknown} args - Additional arguments passed to `getFrame()`.
|
|
141
|
+
* @returns {Promise<void>}
|
|
142
|
+
*/
|
|
89
143
|
async final(...args) {
|
|
90
144
|
if (this.isDone) return;
|
|
91
145
|
await this.writeFrame('finished', ...args);
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import {Writable} from 'node:stream';
|
|
2
|
+
import {StringsInput} from '../strings.js';
|
|
3
|
+
|
|
4
|
+
/** Options for `Writer.write()`. */
|
|
5
|
+
export interface WriteOptions {
|
|
6
|
+
/** If true or 'save', keep cursor in the same column after writing. */
|
|
7
|
+
sameColumn?: boolean | 'save';
|
|
8
|
+
/** If true, omit the trailing newline on the last line. */
|
|
9
|
+
noLastNewLine?: boolean;
|
|
10
|
+
/** String prepended before each line. */
|
|
11
|
+
beforeLine?: string;
|
|
12
|
+
/** String appended after each line. */
|
|
13
|
+
afterLine?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Manages writing to a stream with cursor manipulation and color depth awareness. */
|
|
17
|
+
export class Writer {
|
|
18
|
+
/** The underlying writable stream. */
|
|
19
|
+
stream: Writable;
|
|
20
|
+
/** Forced color depth override, or undefined to auto-detect. */
|
|
21
|
+
forceColorDepth: number | undefined;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param stream - Writable stream (default: stdout).
|
|
25
|
+
* @param forceColorDepth - Override color depth.
|
|
26
|
+
*/
|
|
27
|
+
constructor(stream?: Writable, forceColorDepth?: number);
|
|
28
|
+
|
|
29
|
+
/** Whether the stream is a TTY. */
|
|
30
|
+
readonly isTTY: boolean;
|
|
31
|
+
/** Terminal width in columns. */
|
|
32
|
+
readonly columns: number;
|
|
33
|
+
/** Terminal height in rows. */
|
|
34
|
+
readonly rows: number;
|
|
35
|
+
/** Terminal dimensions. */
|
|
36
|
+
readonly size: {columns: number; rows: number};
|
|
37
|
+
|
|
38
|
+
/** Returns the color depth of the stream.
|
|
39
|
+
* @param env - Environment variables to check (default: `process.env`).
|
|
40
|
+
* @returns Color depth (1, 4, 8, or 24).
|
|
41
|
+
*/
|
|
42
|
+
getColorDepth(env?: object): number;
|
|
43
|
+
/** Checks if the stream supports the given number of colors.
|
|
44
|
+
* @param count - Number of colors to check for.
|
|
45
|
+
* @param env - Environment variables to check.
|
|
46
|
+
* @returns True if supported.
|
|
47
|
+
*/
|
|
48
|
+
hasColors(count?: number, env?: object): boolean;
|
|
49
|
+
hasColors(env?: object): boolean;
|
|
50
|
+
|
|
51
|
+
/** Clears the current line.
|
|
52
|
+
* @param dir - Direction: -1 left, 0 entire, 1 right.
|
|
53
|
+
* @returns Promise resolving when done.
|
|
54
|
+
*/
|
|
55
|
+
clearLine(dir: number): Promise<boolean>;
|
|
56
|
+
/** Clears from cursor to end of screen.
|
|
57
|
+
* @returns Promise resolving when done.
|
|
58
|
+
*/
|
|
59
|
+
clearScreenDown(): Promise<boolean>;
|
|
60
|
+
|
|
61
|
+
/** Moves cursor to absolute position.
|
|
62
|
+
* @param x - Column.
|
|
63
|
+
* @param y - Row (optional).
|
|
64
|
+
* @returns Promise resolving when done.
|
|
65
|
+
*/
|
|
66
|
+
cursorTo(x: number, y?: number): Promise<boolean>;
|
|
67
|
+
/** Moves cursor relative to current position.
|
|
68
|
+
* @param dx - Columns.
|
|
69
|
+
* @param dy - Rows.
|
|
70
|
+
* @returns Promise resolving when done.
|
|
71
|
+
*/
|
|
72
|
+
moveCursor(dx: number, dy: number): Promise<boolean>;
|
|
73
|
+
|
|
74
|
+
/** Writes a raw string to the stream.
|
|
75
|
+
* @param s - The string to write.
|
|
76
|
+
* @returns Promise resolving when done.
|
|
77
|
+
*/
|
|
78
|
+
writeString(s: string): Promise<void>;
|
|
79
|
+
/** Writes a Box/string to the stream with optional cursor control.
|
|
80
|
+
* @param s - Input: Box, string, or string array.
|
|
81
|
+
* @param options - Write options.
|
|
82
|
+
* @returns Promise resolving when done.
|
|
83
|
+
*/
|
|
84
|
+
write(s: StringsInput, options?: WriteOptions): Promise<void>;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export default Writer;
|
package/src/output/writer.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
1
|
import process from 'node:process';
|
|
4
2
|
|
|
5
3
|
import {CURSOR_DOWN1, CURSOR_RESTORE_POS, CURSOR_SAVE_POS} from '../ansi/csi.js';
|
|
@@ -8,33 +6,57 @@ import {getLength, matchCsiNoGroups, matchCsiNoSgrNoGroups, toStrings} from '../
|
|
|
8
6
|
const write = async (stream, chunk, encoding = 'utf8') =>
|
|
9
7
|
new Promise((resolve, reject) => stream.write(chunk, encoding, error => (error ? reject(error) : resolve())));
|
|
10
8
|
|
|
9
|
+
/** Abstracts writing to a stream (defaulting to `process.stdout`).
|
|
10
|
+
* Handles TTY capabilities, color depth, cursor manipulation, and ANSI stripping for non-TTY streams.
|
|
11
|
+
*/
|
|
11
12
|
export class Writer {
|
|
13
|
+
/**
|
|
14
|
+
* @param {import('node:stream').Writable} [stream=process.stdout] - The output stream.
|
|
15
|
+
* @param {number} [forceColorDepth] - Force a specific color depth instead of auto-detecting.
|
|
16
|
+
*/
|
|
12
17
|
constructor(stream = process.stdout, forceColorDepth) {
|
|
13
18
|
this.stream = stream;
|
|
14
19
|
this.forceColorDepth = forceColorDepth;
|
|
15
20
|
}
|
|
16
21
|
|
|
22
|
+
/** Whether the stream is a TTY. */
|
|
17
23
|
get isTTY() {
|
|
18
24
|
return this.stream.isTTY;
|
|
19
25
|
}
|
|
26
|
+
/** The number of columns in the terminal. */
|
|
20
27
|
get columns() {
|
|
21
28
|
return this.stream.columns;
|
|
22
29
|
}
|
|
30
|
+
/** The number of rows in the terminal. */
|
|
23
31
|
get rows() {
|
|
24
32
|
return this.stream.rows;
|
|
25
33
|
}
|
|
34
|
+
/** The terminal size as `{columns, rows}`. */
|
|
26
35
|
get size() {
|
|
27
36
|
const [columns, rows] = this.stream.getWindowSize?.() || [];
|
|
28
37
|
return {columns, rows};
|
|
29
38
|
}
|
|
39
|
+
/** Returns the color depth of the stream.
|
|
40
|
+
* @param {object} [env] - Environment variables to check (default: `process.env`).
|
|
41
|
+
* @returns {number} The color depth (1, 4, 8, or 24).
|
|
42
|
+
*/
|
|
30
43
|
getColorDepth(...args) {
|
|
31
44
|
return this.forceColorDepth || this.stream.getColorDepth?.(...args);
|
|
32
45
|
}
|
|
33
46
|
|
|
47
|
+
/** Checks if the stream supports the given number of colors.
|
|
48
|
+
* @param {number} [count] - Number of colors to check for.
|
|
49
|
+
* @param {object} [env] - Environment variables to check.
|
|
50
|
+
* @returns {boolean} True if supported.
|
|
51
|
+
*/
|
|
34
52
|
hasColors(...args) {
|
|
35
53
|
return this.forceColorDepth ? args[0] <= Math.pow(2, this.forceColorDepth) : this.stream.hasColors?.(...args);
|
|
36
54
|
}
|
|
37
55
|
|
|
56
|
+
/** Clears the current line.
|
|
57
|
+
* @param {number} dir - Direction: -1 = left, 0 = entire line, 1 = right.
|
|
58
|
+
* @returns {Promise<boolean>} True if the operation was supported.
|
|
59
|
+
*/
|
|
38
60
|
clearLine(dir) {
|
|
39
61
|
return new Promise((resolve, reject) => {
|
|
40
62
|
if (typeof this.stream.clearLine == 'function') {
|
|
@@ -44,6 +66,9 @@ export class Writer {
|
|
|
44
66
|
}
|
|
45
67
|
});
|
|
46
68
|
}
|
|
69
|
+
/** Clears the screen from the cursor down.
|
|
70
|
+
* @returns {Promise<boolean>} True if the operation was supported.
|
|
71
|
+
*/
|
|
47
72
|
clearScreenDown() {
|
|
48
73
|
return new Promise((resolve, reject) => {
|
|
49
74
|
if (typeof this.stream.clearScreenDown == 'function') {
|
|
@@ -54,6 +79,11 @@ export class Writer {
|
|
|
54
79
|
});
|
|
55
80
|
}
|
|
56
81
|
|
|
82
|
+
/** Moves the cursor to an absolute position.
|
|
83
|
+
* @param {number} x - Column.
|
|
84
|
+
* @param {number} [y] - Row.
|
|
85
|
+
* @returns {Promise<boolean>} True if the operation was supported.
|
|
86
|
+
*/
|
|
57
87
|
cursorTo(x, y) {
|
|
58
88
|
return new Promise((resolve, reject) => {
|
|
59
89
|
if (typeof this.stream.cursorTo == 'function') {
|
|
@@ -63,6 +93,11 @@ export class Writer {
|
|
|
63
93
|
}
|
|
64
94
|
});
|
|
65
95
|
}
|
|
96
|
+
/** Moves the cursor relative to its current position.
|
|
97
|
+
* @param {number} dx - Columns to move.
|
|
98
|
+
* @param {number} dy - Rows to move.
|
|
99
|
+
* @returns {Promise<boolean>} True if the operation was supported.
|
|
100
|
+
*/
|
|
66
101
|
moveCursor(dx, dy) {
|
|
67
102
|
return new Promise((resolve, reject) => {
|
|
68
103
|
if (typeof this.stream.moveCursor == 'function') {
|
|
@@ -73,6 +108,10 @@ export class Writer {
|
|
|
73
108
|
});
|
|
74
109
|
}
|
|
75
110
|
|
|
111
|
+
/** Writes a raw string to the stream, stripping ANSI codes for non-TTY streams.
|
|
112
|
+
* @param {string} s - The string to write.
|
|
113
|
+
* @returns {Promise<void>}
|
|
114
|
+
*/
|
|
76
115
|
async writeString(s) {
|
|
77
116
|
s = String(s);
|
|
78
117
|
|
|
@@ -93,12 +132,23 @@ export class Writer {
|
|
|
93
132
|
await write(this.stream, s.replace(matchCsiNoGroups, ''));
|
|
94
133
|
}
|
|
95
134
|
|
|
135
|
+
/** Writes a text container to the stream.
|
|
136
|
+
* @param {import('../strings.js').StringsInput} s - Input convertible to strings.
|
|
137
|
+
* @param {object} [options] - Options.
|
|
138
|
+
* @param {boolean|'save'} [options.sameColumn] - If true or 'save', keep cursor in the same column between lines.
|
|
139
|
+
* @param {boolean} [options.noLastNewLine] - If true, omit the trailing newline.
|
|
140
|
+
* @param {string} [options.beforeLine=''] - String prepended to each line.
|
|
141
|
+
* @param {string} [options.afterLine=''] - String appended to each line.
|
|
142
|
+
* @returns {Promise<void>}
|
|
143
|
+
*/
|
|
96
144
|
async write(s, {sameColumn, noLastNewLine, beforeLine = '', afterLine = ''} = {}) {
|
|
97
145
|
s = toStrings(s);
|
|
98
146
|
|
|
99
147
|
if (!this.isTTY) {
|
|
100
148
|
const matcher = this.forceColorDepth ? matchCsiNoSgrNoGroups : matchCsiNoGroups;
|
|
101
|
-
let lines = Array.from(s)
|
|
149
|
+
let lines = Array.from(s)
|
|
150
|
+
.map(line => beforeLine + line + afterLine)
|
|
151
|
+
.join('\n');
|
|
102
152
|
if (!noLastNewLine) lines += '\n';
|
|
103
153
|
matcher.lastIndex = 0;
|
|
104
154
|
lines = lines.replace(matcher, '');
|
|
@@ -108,7 +158,7 @@ export class Writer {
|
|
|
108
158
|
|
|
109
159
|
if (sameColumn === 'save') {
|
|
110
160
|
for (const line of s) {
|
|
111
|
-
await write(this.stream, CURSOR_SAVE_POS + beforeLine + line +
|
|
161
|
+
await write(this.stream, CURSOR_SAVE_POS + beforeLine + line + afterLine + CURSOR_RESTORE_POS + CURSOR_DOWN1);
|
|
112
162
|
}
|
|
113
163
|
return;
|
|
114
164
|
}
|
|
@@ -123,7 +173,9 @@ export class Writer {
|
|
|
123
173
|
return;
|
|
124
174
|
}
|
|
125
175
|
|
|
126
|
-
let lines = Array.from(s)
|
|
176
|
+
let lines = Array.from(s)
|
|
177
|
+
.map(line => beforeLine + line + afterLine)
|
|
178
|
+
.join('\n');
|
|
127
179
|
if (!noLastNewLine) lines += '\n';
|
|
128
180
|
await write(this.stream, lines);
|
|
129
181
|
}
|