@peteanderson/ansi 0.2.4 → 0.3.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.
Files changed (84) hide show
  1. package/README.md +119 -28
  2. package/dist/ansi.d.ts +46 -0
  3. package/dist/ansi.js +49 -0
  4. package/dist/caret.d.ts +28 -0
  5. package/dist/caret.js +70 -0
  6. package/dist/erase.d.ts +13 -0
  7. package/dist/erase.js +30 -0
  8. package/dist/features/build.d.ts +11 -0
  9. package/dist/features/build.js +50 -0
  10. package/dist/features/detect.d.ts +4 -0
  11. package/dist/features/detect.js +33 -0
  12. package/dist/features/features.d.ts +5 -0
  13. package/dist/features/features.js +39 -0
  14. package/dist/features/index.d.ts +2 -0
  15. package/dist/features/index.js +2 -0
  16. package/dist/index.d.ts +57 -0
  17. package/dist/index.js +2 -0
  18. package/dist/patterns.d.ts +3 -0
  19. package/dist/patterns.js +43 -0
  20. package/dist/scroll.d.ts +6 -0
  21. package/dist/scroll.js +14 -0
  22. package/dist/sgr/color/color.d.ts +9 -0
  23. package/dist/sgr/color/color.js +76 -0
  24. package/dist/sgr/color/index.d.ts +2 -0
  25. package/dist/sgr/color/index.js +2 -0
  26. package/dist/sgr/color/utils.d.ts +27 -0
  27. package/dist/sgr/color/utils.js +67 -0
  28. package/dist/sgr/index.d.ts +9 -0
  29. package/dist/sgr/index.js +6 -0
  30. package/dist/sgr/reset.d.ts +1 -0
  31. package/dist/sgr/reset.js +3 -0
  32. package/dist/sgr/sgr.d.ts +14 -0
  33. package/dist/sgr/sgr.js +57 -0
  34. package/dist/sgr/style.d.ts +13 -0
  35. package/dist/sgr/style.js +19 -0
  36. package/dist/sgr/utils.d.ts +2 -0
  37. package/dist/sgr/utils.js +16 -0
  38. package/dist/simplify/atom.d.ts +3 -0
  39. package/dist/simplify/atom.js +28 -0
  40. package/dist/simplify/index.d.ts +2 -0
  41. package/dist/simplify/index.js +2 -0
  42. package/dist/simplify/lookup.d.ts +5 -0
  43. package/dist/simplify/lookup.js +77 -0
  44. package/dist/simplify/simplify.d.ts +1 -0
  45. package/dist/simplify/simplify.js +25 -0
  46. package/dist/simplify/state.d.ts +10 -0
  47. package/dist/simplify/state.js +57 -0
  48. package/dist/simplify/utils.d.ts +2 -0
  49. package/dist/simplify/utils.js +31 -0
  50. package/dist/slice.d.ts +1 -0
  51. package/dist/slice.js +83 -0
  52. package/dist/strip.d.ts +15 -0
  53. package/dist/strip.js +18 -0
  54. package/package.json +13 -20
  55. package/src/ansi.js +58 -0
  56. package/src/caret.js +86 -0
  57. package/src/erase.js +34 -0
  58. package/src/features/build.js +60 -0
  59. package/src/features/detect.js +39 -0
  60. package/src/features/features.js +53 -0
  61. package/src/features/index.js +1 -0
  62. package/src/features/types.d.ts +9 -0
  63. package/src/index.js +1 -0
  64. package/src/patterns.js +44 -0
  65. package/src/scroll.js +18 -0
  66. package/src/sgr/color/color.js +104 -0
  67. package/src/sgr/color/index.js +1 -0
  68. package/src/sgr/color/types.d.ts +28 -0
  69. package/src/sgr/color/utils.js +81 -0
  70. package/src/sgr/index.js +5 -0
  71. package/src/sgr/reset.js +3 -0
  72. package/src/sgr/sgr.js +77 -0
  73. package/src/sgr/style.js +23 -0
  74. package/src/sgr/utils.js +20 -0
  75. package/src/simplify/atom.js +34 -0
  76. package/src/simplify/index.js +1 -0
  77. package/src/simplify/lookup.js +86 -0
  78. package/src/simplify/simplify.js +33 -0
  79. package/src/simplify/state.js +73 -0
  80. package/src/simplify/types.d.ts +28 -0
  81. package/src/simplify/utils.js +41 -0
  82. package/src/slice.js +99 -0
  83. package/src/strip.js +23 -0
  84. package/ansi.js +0 -28
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ /** @type {<T>(a: T[] | undefined, b: T[] | undefined) => boolean} */
3
+ function arraysEqual(a, b) {
4
+ if (a === b)
5
+ return true;
6
+ if (!a || !b || a.length !== b.length)
7
+ return false;
8
+ for (let i = 0; i < a.length; i++) {
9
+ if (a[i] !== b[i])
10
+ return false;
11
+ }
12
+ return true;
13
+ }
14
+ /** @type {(previous: number[] | undefined, current: number[]) => number[]} */
15
+ function transitionIntensity(previous, current) {
16
+ if (!previous || previous[0] === 22 || current[0] === 22)
17
+ return current;
18
+ const oldBold = previous[0] === 1;
19
+ const newBold = current[0] === 1;
20
+ const oldDim = previous[previous.length - 1] === 2;
21
+ const newDim = current[current.length - 1] === 2;
22
+ const result = [];
23
+ if (oldBold && !newBold || oldDim && !newDim)
24
+ result.push(22);
25
+ if (newBold && (!oldBold || result[0] === 22))
26
+ result.push(1);
27
+ if (newDim && (!oldDim || result[0] === 22))
28
+ result.push(2);
29
+ return result;
30
+ }
31
+ module.exports = { arraysEqual, transitionIntensity };
@@ -0,0 +1 @@
1
+ export function slice(text: string, start: number, end: number): string;
package/dist/slice.js ADDED
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ const { rxCSI, rxSGR } = require("./patterns");
3
+ const { simplify } = require("./simplify");
4
+ const { visibleLength } = require("./strip");
5
+ /**
6
+ @type {
7
+ (
8
+ text : string,
9
+ fromStringIndex : number,
10
+ visibleCount : number,
11
+ ) => {index: number, length: number}
12
+ }
13
+ */
14
+ function scanCSI(text, fromStringIndex, visibleCount) {
15
+ // We intentionally mutate `lastIndex` here, so we need a clone.
16
+ const rx = new RegExp(rxCSI);
17
+ let length = 0;
18
+ let index = fromStringIndex;
19
+ while (index < text.length && length < visibleCount) {
20
+ if (text[index] === "\x1b") {
21
+ rx.lastIndex = index;
22
+ const match = rx.exec(text);
23
+ if (match?.index === index) {
24
+ index += match[0].length;
25
+ continue;
26
+ }
27
+ }
28
+ index++;
29
+ length++;
30
+ }
31
+ return { index, length };
32
+ }
33
+ /** @type {(text: string, start: number, end?: number) => string} */
34
+ function skippedSequences(text, start, end) {
35
+ const sequences = text.slice(start, end).match(new RegExp(rxSGR));
36
+ return sequences ? simplify(sequences.join("")) : "";
37
+ }
38
+ /**
39
+ * Return a slice of the given text, counting only visible characters (i.e. ignoring ANSI escape
40
+ * codes). The returned slice will include any ANSI escape codes from within the slice. In addition,
41
+ * SGR codes (m) from before and after the slice will be combined, simplified, and included so that
42
+ * the terminal state before and after the slice will be the same as if the whole string were
43
+ * printed. If you compose two or more slices together, there may be redundant SGR sequences at the
44
+ * boundaries, but the result will be correct. You can use `simplify` to remove the redundancies.
45
+ *
46
+ * Note the following caveats:
47
+ *
48
+ * * Any non-SGR ANSI codes prior to or after the slice will be ignored.
49
+ * * Control characters (such as newlines and backspaces) both outside and inside the slice are
50
+ * counted as visible characters.
51
+ *
52
+ * These caveats mean that text may not appear on screen to be the same number of characters as the
53
+ * length of the slice, and that the terminal state may not be exactly the same at the start and end
54
+ * of the slice as it would be if the whole string were printed. This function is intended mostly
55
+ * for text that contains SGR codes, but no other ANSI codes or control characters.
56
+ *
57
+ * @type {(text: string, start: number, end: number) => string}
58
+ */
59
+ function slice(text, start, end) {
60
+ /** @type {number | undefined} */
61
+ let textVisibleLength;
62
+ const getVisibleLength = () => textVisibleLength ??= visibleLength(text);
63
+ if (start < 0 || end < 0) {
64
+ textVisibleLength = getVisibleLength();
65
+ if (start < 0)
66
+ start = Math.max(0, textVisibleLength + start);
67
+ if (end < 0)
68
+ end = Math.max(0, textVisibleLength + end);
69
+ }
70
+ if (start > end)
71
+ return "";
72
+ if (start === 0 && (end >= text.length || end >= getVisibleLength()))
73
+ return text;
74
+ const { index: startIndex, length: visibleSkipped } = scanCSI(text, 0, start);
75
+ const endIndex = startIndex < text.length && visibleSkipped < end
76
+ ? scanCSI(text, startIndex, end - visibleSkipped).index
77
+ : text.length;
78
+ // Start and end the string in the same terminal state it would be in if the string weren't sliced.
79
+ const leadingSequences = skippedSequences(text, 0, startIndex);
80
+ const trailingSequences = skippedSequences(text, endIndex);
81
+ return leadingSequences + text.slice(startIndex, endIndex) + trailingSequences;
82
+ }
83
+ module.exports = { slice };
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Remove all ANSI escape codes from the given text.
3
+ * @type {(text: string) => string}
4
+ */
5
+ export const strip: (text: string) => string;
6
+ /**
7
+ * Calculate the visible length of a string with ANSI escape codes stripped.
8
+ * @type {(text: string) => number}
9
+ */
10
+ export const visibleLength: (text: string) => number;
11
+ /**
12
+ * Remove all ANSI escape codes except SGR codes (m), which are used for color and styling.
13
+ * @type {(text: string) => string}
14
+ */
15
+ export const sanitize: (text: string) => string;
package/dist/strip.js ADDED
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ const { rxCSI, rxUnsafe } = require("./patterns");
3
+ /**
4
+ * Remove all ANSI escape codes from the given text.
5
+ * @type {(text: string) => string}
6
+ */
7
+ const strip = text => text.replace(new RegExp(rxCSI), "");
8
+ /**
9
+ * Calculate the visible length of a string with ANSI escape codes stripped.
10
+ * @type {(text: string) => number}
11
+ */
12
+ const visibleLength = text => strip(text).length;
13
+ /**
14
+ * Remove all ANSI escape codes except SGR codes (m), which are used for color and styling.
15
+ * @type {(text: string) => string}
16
+ */
17
+ const sanitize = text => text.replace(new RegExp(rxUnsafe), "");
18
+ module.exports = { strip, visibleLength, sanitize };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name" : "@peteanderson/ansi",
3
- "version" : "0.2.4",
3
+ "version" : "0.3.0",
4
4
  "description" : "ANSI escape sequence generation with automatic feature detection",
5
5
  "author" : {
6
6
  "name" : "Pete Anderson",
@@ -8,32 +8,25 @@
8
8
  },
9
9
  "repository" : {
10
10
  "type" : "git",
11
- "url" : "https://github.com/anderson-pete/ansi"
11
+ "url" : "git+https://github.com/anderson-pete/ansi.git"
12
12
  },
13
- "keywords" : [
14
- "ansi",
15
- "nodejs",
16
- "color",
17
- "terminal",
18
- "cli"
19
- ],
20
- "engines" : {
21
- "node": ">=18"
22
- },
23
- "files" : [
24
- "."
25
- ],
26
- "main" : "ansi.js",
27
- "types" : "ansi.d.ts",
13
+ "keywords" : ["ansi", "nodejs", "color", "terminal", "cli"],
14
+ "engines" : {"node": ">=18"},
15
+ "files" : ["dist", "src"],
16
+ "main" : "dist/ansi.js",
17
+ "types" : "dist/ansi.d.ts",
28
18
  "exports" : {
29
19
  ".": {
30
- "require": "./ansi.js"
20
+ "types" : "./dist/ansi.d.ts",
21
+ "require" : "./dist/ansi.js"
31
22
  }
32
23
  },
33
24
  "license" : "MIT",
34
25
  "scripts" : {
35
- "build" : "rm *.d.ts && tsc --allowJs --declaration --emitDeclarationOnly --moduleResolution node --ignoreDeprecations 6.0 ansi.js",
36
- "tag" : "git tag -a v$npm_package_version -m v$npm_package_version"
26
+ "prebuild" : "node -e \"require('node:fs').rmSync('dist', {force: true, recursive: true})\"",
27
+ "build" : "tsc -p tsconfig.build.json",
28
+ "prepack" : "npm run build",
29
+ "tag" : "git tag -a v$npm_package_version -m v$npm_package_version"
37
30
  },
38
31
  "devDependencies" : {
39
32
  "@types/node" : "^26.0.0",
package/src/ansi.js ADDED
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+
3
+ const {makeCaret} = require("./caret");
4
+ const {makeErase} = require("./erase");
5
+ const {getFeatures} = require("./features");
6
+ const {makeScroll} = require("./scroll");
7
+ const {makeColor, makeReset, makeStyle} = require("./sgr");
8
+ const {simplify} = require("./simplify");
9
+ const {slice} = require("./slice");
10
+ const {sanitize, strip, visibleLength} = require("./strip");
11
+
12
+ /**
13
+ @typedef {import("./features/features").Args} Args
14
+
15
+ @typedef {ReturnType<typeof makeColor> & {
16
+ caret : ReturnType<typeof makeCaret>;
17
+ erase : ReturnType<typeof makeErase>;
18
+ scroll : ReturnType<typeof makeScroll>;
19
+
20
+ reset : string;
21
+ style : ReturnType<typeof makeStyle>;
22
+
23
+ strip : typeof strip;
24
+ visibleLength : typeof visibleLength;
25
+ sanitize : typeof sanitize;
26
+ slice : typeof slice;
27
+ simplify : typeof simplify;
28
+
29
+ features : ReturnType<typeof getFeatures>;
30
+ }} Ansi
31
+ */
32
+
33
+ /** @type {(...args: Args) => Ansi}*/
34
+ function makeAnsi(...args) {
35
+ const features = getFeatures(...args);
36
+
37
+ return {
38
+ caret : makeCaret(features.caret),
39
+ erase : makeErase(features.erase),
40
+ scroll : makeScroll(features.scroll),
41
+
42
+ reset : makeReset(features.colorDepth > 1),
43
+ style : makeStyle(features.style),
44
+ ...makeColor(features.colorDepth),
45
+
46
+ strip,
47
+ visibleLength,
48
+ sanitize,
49
+ slice,
50
+ simplify,
51
+
52
+ features,
53
+ };
54
+ }
55
+
56
+ const ansi = Object.assign(makeAnsi, makeAnsi());
57
+
58
+ module.exports = ansi;
package/src/caret.js ADDED
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+
3
+ /** @type {(code: string) => (count?: number) => string} */
4
+ const move = code => count => `\x1b[${count === undefined ? "" : count ? count : "?"}${code}`;
5
+
6
+ /** @type {(count?: number) => string} */
7
+ const noop = () => "";
8
+
9
+ const caret = {
10
+ up : move("A"),
11
+ down : move("B"),
12
+ forward : move("C"),
13
+ backward : move("D"),
14
+ nextLine : move("E"),
15
+ prevLine : move("F"),
16
+
17
+ /** @type {(x: number) => string} */
18
+ x: x => `\x1b[${x}G`,
19
+
20
+ // These are the old VT100 codes, but they're widely supported by old and new terminals. The
21
+ // newer VT220 codes are standardized by ECMA and ISO, and are also widely supported, but might
22
+ // not work in some older terminals. The VT220 codes are:
23
+ //
24
+ // ```
25
+ // save : "\x1b[s",
26
+ // restore : "\x1b[u",
27
+ // ```
28
+ save : "\x1b7",
29
+ restore : "\x1b8",
30
+
31
+ hide : "\x1b[?25l",
32
+ show : "\x1b[?25h",
33
+
34
+ position: {
35
+ get: "\x1b[6n",
36
+ /** @type {(x: number, y: number) => string} */
37
+ set: (x, y) => `\x1b[${y};${x}H`,
38
+ },
39
+
40
+ shape: {
41
+ steadyBlock : "\x1b[2 q",
42
+ steadyBar : "\x1b[6 q",
43
+ steadyUnderline : "\x1b[4 q",
44
+ blinkingBlock : "\x1b[1 q",
45
+ blinkingBar : "\x1b[5 q",
46
+ blinkingUnderline : "\x1b[3 q",
47
+ default : "\x1b[0 q",
48
+ },
49
+ };
50
+
51
+ /** @type {typeof caret} */
52
+ const disabled = {
53
+ up : noop,
54
+ down : noop,
55
+ forward : noop,
56
+ backward : noop,
57
+ nextLine : noop,
58
+ prevLine : noop,
59
+
60
+ x: noop,
61
+
62
+ save : "",
63
+ restore : "",
64
+
65
+ hide : "",
66
+ show : "",
67
+
68
+ position: {
69
+ get: "",
70
+ set: noop,
71
+ },
72
+
73
+ shape: {
74
+ steadyBlock : "",
75
+ steadyBar : "",
76
+ steadyUnderline : "",
77
+ blinkingBlock : "",
78
+ blinkingBar : "",
79
+ blinkingUnderline : "",
80
+ default : "",
81
+ },
82
+ };
83
+
84
+ const makeCaret = (enabled = true) => enabled ? caret : disabled;
85
+
86
+ module.exports = {makeCaret};
package/src/erase.js ADDED
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+
3
+ const erase = {
4
+ line: {
5
+ toStart: "\x1b[1K",
6
+ toEnd : "\x1b[0K",
7
+ full : "\x1b[2K",
8
+ },
9
+ screen: {
10
+ toStart : "\x1b[1J",
11
+ toEnd : "\x1b[0J",
12
+ full : "\x1b[2J",
13
+ scrollback : "\x1b[3J",
14
+ },
15
+ };
16
+
17
+ /** @type {typeof erase} */
18
+ const disabled = {
19
+ line: {
20
+ toStart : "",
21
+ toEnd : "",
22
+ full : "",
23
+ },
24
+ screen: {
25
+ toStart : "",
26
+ toEnd : "",
27
+ full : "",
28
+ scrollback : "",
29
+ },
30
+ };
31
+
32
+ const makeErase = (enabled = true) => enabled ? erase : disabled;
33
+
34
+ module.exports = {makeErase};
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+
3
+ const {getColorDepth, detectFeatureSupport} = require("./detect");
4
+
5
+ /** @typedef {import("./types").ColorDepth} ColorDepth */
6
+ /** @typedef {import("./types").Features} Features */
7
+
8
+ /** @type {(colorDepth: number) => ColorDepth} */
9
+ function normalizeColorDepth(colorDepth) {
10
+ colorDepth = Math.floor(colorDepth);
11
+ if (colorDepth >= 24)
12
+ return 24;
13
+ if (colorDepth >= 8)
14
+ return 8;
15
+ if (colorDepth >= 4)
16
+ return 4;
17
+ if (colorDepth === 3)
18
+ return 3;
19
+ return 1;
20
+ }
21
+
22
+ /**
23
+ @type {
24
+ (
25
+ isTTY : boolean,
26
+ colorDepth : ColorDepth,
27
+ style? : boolean,
28
+ caret? : boolean,
29
+ erase? : boolean,
30
+ scroll? : boolean,
31
+ ) => Features
32
+ }
33
+ */
34
+ function build(isTTY, colorDepth, style, caret, erase, scroll) {
35
+ if (style !== undefined && caret !== undefined && erase !== undefined && scroll !== undefined)
36
+ return {colorDepth: normalizeColorDepth(colorDepth), style, caret, erase, scroll};
37
+
38
+ const support = detectFeatureSupport(isTTY, colorDepth);
39
+ return {
40
+ colorDepth : normalizeColorDepth(colorDepth),
41
+ style : style ?? support.style,
42
+ caret : caret ?? support.caret,
43
+ erase : erase ?? support.erase,
44
+ scroll : scroll ?? support.scroll,
45
+ };
46
+ }
47
+
48
+ /** @type {(isTTY: boolean, enabled: boolean) => Features} */
49
+ const boolean = (isTTY, enabled) => build(isTTY, enabled ? 4 : 1);
50
+
51
+ /** @type {(isTTY: boolean, colorDepth: ColorDepth) => Features} */
52
+ const number = (isTTY, colorDepth) => build(isTTY, colorDepth);
53
+
54
+ /** @type {(stream: NodeJS.WriteStream) => Features} */
55
+ const tty = stream => build(true, getColorDepth(stream));
56
+
57
+ /** @type {() => Features} */
58
+ const pipe = () => build(false, getColorDepth());
59
+
60
+ module.exports = {build, boolean, number, tty, pipe};
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+
3
+ /** @typedef {import("./types").ColorDepth} ColorDepth */
4
+ /** @typedef {import("./types").Features} Features */
5
+
6
+ /** @type {(stream?: NodeJS.WriteStream) => ColorDepth} */
7
+ function getColorDepth(stream) {
8
+ if (stream?.isTTY)
9
+ return /** @type {ColorDepth} */(stream.getColorDepth());
10
+
11
+ if (process.env.NO_COLOR || process.env.NODE_DISABLE_COLORS)
12
+ return 1;
13
+
14
+ switch (process.env.FORCE_COLOR) {
15
+ case "":
16
+ case "true":
17
+ case "1":
18
+ return 4;
19
+ case "2":
20
+ return 8;
21
+ case "3":
22
+ return 24;
23
+ default:
24
+ return 1;
25
+ }
26
+ }
27
+
28
+ /** @type {(isTTY: boolean, colorDepth: ColorDepth) => Omit<Features, "colorDepth">} */
29
+ function detectFeatureSupport(isTTY, colorDepth) {
30
+ const enabled = process.env.TERM !== "dumb" && isTTY;
31
+ return {
32
+ style : colorDepth > 1,
33
+ caret : enabled,
34
+ erase : enabled,
35
+ scroll : enabled,
36
+ };
37
+ }
38
+
39
+ module.exports = {detectFeatureSupport, getColorDepth}
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+
3
+ const {Socket} = require("node:net");
4
+ const {WriteStream} = require("node:tty");
5
+ const {build, boolean, number, tty, pipe} = require("./build");
6
+ const {getColorDepth} = require("./detect");
7
+
8
+ /**
9
+ @typedef {import("./types").ColorDepth} ColorDepth
10
+ @typedef {import("./types").Features} Features
11
+
12
+ @typedef {
13
+ | [enabled : boolean, stream?: NodeJS.WriteStream]
14
+ | [colorDepth : ColorDepth, stream?: NodeJS.WriteStream]
15
+ | [features : Partial<Features>, stream?: NodeJS.WriteStream]
16
+ | [socket : Socket]
17
+ | [stream? : NodeJS.WriteStream]
18
+ } Args
19
+ */
20
+
21
+ /** @type {(...args: Args) => Features} */
22
+ function getFeatures(...args) {
23
+ let [features, stream] = args;
24
+
25
+ if (typeof features === "boolean")
26
+ return boolean((stream ?? process.stdout).isTTY, features);
27
+
28
+ if (typeof features === "number")
29
+ return number((stream ?? process.stdout).isTTY, features);
30
+
31
+ if (!features || typeof features !== "object")
32
+ features = (stream ?? process.stdout);
33
+
34
+ if (features instanceof WriteStream)
35
+ return tty(features);
36
+
37
+ if (features instanceof Socket)
38
+ return pipe();
39
+
40
+ const {colorDepth, style, caret, erase, scroll} = features;
41
+
42
+ stream ??= process.stdout;
43
+
44
+ if (colorDepth)
45
+ build(stream.isTTY, colorDepth, style, caret, erase, scroll);
46
+
47
+ if (stream.isTTY)
48
+ return build(true, getColorDepth(stream), style, caret, erase, scroll);
49
+
50
+ return build(false, getColorDepth(), style, caret, erase, scroll);
51
+ }
52
+
53
+ module.exports = {getFeatures};
@@ -0,0 +1 @@
1
+ module.exports = require("./features");
@@ -0,0 +1,9 @@
1
+ export type ColorDepth = 1 | 3 | 4 | 8 | 24;
2
+
3
+ export interface Features {
4
+ colorDepth : ColorDepth;
5
+ style : boolean;
6
+ caret : boolean;
7
+ erase : boolean;
8
+ scroll : boolean;
9
+ }
package/src/index.js ADDED
@@ -0,0 +1 @@
1
+ module.exports = require("./ansi");
@@ -0,0 +1,44 @@
1
+ /*
2
+ I'm freezing these to prevent accidental mutation of the `lastIndex` property, which could keep
3
+ future calls from starting at the beginning of the string.
4
+
5
+ Code using these objects will have to clone them (with `new RegExp(rx)`) before using functions
6
+ that modify `lastIndex` (such as `exec`). But according to my benchmarks, cloning the regex takes
7
+ mere nanoseconds, and it wasn't even measurable when operating on longer strings. V8 clearly uses a
8
+ global cache of compiled regexes. I assume that SpiderMonkey does the same thing.
9
+
10
+ Here are my `timeit` benchmark results:
11
+
12
+ ```js
13
+ const {timeit} = require("$timeit");
14
+
15
+ const rxSGR = /\x1b\[[\d;]*m/g;
16
+ const short = "\x1b[31mred \x1b[32mgreen \x1b[34mblue \x1b[0m";
17
+ const long = short.repeat(1000);
18
+
19
+ timeit(() => short.replace(rxSGR, ""), {description: "short - no clone "});
20
+ timeit(() => short.replace(new RegExp(rxSGR), ""), {description: "short - clone object "});
21
+ timeit(() => short.replace(new RegExp(rxSGR.source, "g"), ""), {description: "short - clone source "});
22
+
23
+ console.log();
24
+
25
+ timeit(() => long .replace(rxSGR, ""), {description: "long - no clone "});
26
+ timeit(() => long .replace(new RegExp(rxSGR), ""), {description: "long - clone object "});
27
+ timeit(() => long .replace(new RegExp(rxSGR.source, "g"), ""), {description: "long - clone source "});
28
+
29
+ // Output:
30
+ // short - no clone : 82.73 ns (12,087,731 in 1.000016982 s)
31
+ // short - clone object : 142.132 ns (7,035,692 in 1.0000001770000002 s)
32
+ // short - clone source : 127.872 ns (7,820,293 in 1.00000017 s)
33
+ //
34
+ // long - no clone : 47.953 ųs (20,854 in 1.0000098149999999 s)
35
+ // long - clone object : 46.713 ųs (21,408 in 1.0000285520000003 s)
36
+ // long - clone source : 46.491 ųs (21,510 in 1.000026492 s)
37
+ ```
38
+ */
39
+
40
+ const rxSGR = Object.freeze(/\x1b\[[\d;]*m/g);
41
+ const rxCSI = Object.freeze(/\x1b\[[0-?]*[ -/]*[@-~]/g);
42
+ const rxUnsafe = Object.freeze(/\x1b\[[0-?]*[ -/]*[@-ln-~]/g); // Everything except SGR codes (m).
43
+
44
+ module.exports = {rxSGR, rxCSI, rxUnsafe};
package/src/scroll.js ADDED
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+
3
+ const scroll = {
4
+ /** @type {(lines?: number) => string} */
5
+ up : lines => `\x1b[${lines ?? 1}S`,
6
+ /** @type {(lines?: number) => string} */
7
+ down : lines => `\x1b[${lines ?? 1}T`,
8
+ };
9
+
10
+ /** @type {typeof scroll} */
11
+ const disabled = {
12
+ up : () => "",
13
+ down : () => "",
14
+ };
15
+
16
+ const makeScroll = (enabled = true) => enabled ? scroll : disabled;
17
+
18
+ module.exports = {makeScroll};