@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,104 @@
1
+ const {makeSGR} = require("../sgr");
2
+ const {makeStyle} = require("../style");
3
+ const {clip, rgbToX256, rgbToX16, rgbToX8, x256ToRgb} = require("./utils");
4
+
5
+ /** @typedef {import("../sgr").Code} Code */
6
+ /** @typedef {import("../../features/types").ColorDepth} ColorDepth */
7
+ /** @typedef {import("../sgr").SGR} SGR */
8
+ /** @typedef {import("./types").Channel} Channel */
9
+
10
+ const fgOpen = 30, fgClose = 39;
11
+ const bgOpen = 40, bgClose = 49;
12
+
13
+ /** @type {(colorDepth: ColorDepth, open: number, close: number) => Channel} */
14
+ function makeChannel(colorDepth, open, close) {
15
+ const sgrBase = makeSGR(colorDepth > 1);
16
+ const style = makeStyle(colorDepth > 1);
17
+
18
+ /** @type {(code: Code) => SGR} */
19
+ const sgr = code => sgrBase(code, close);
20
+
21
+ /** @type {(index: number) => SGR} */
22
+ const bright =
23
+ colorDepth >= 4 ? index => sgr(open + 60 + index) :
24
+ colorDepth === 3 ?
25
+ open === fgOpen ?
26
+ index => style.bold.combine(sgr(open + index)) :
27
+ index => sgr(open + index) :
28
+ () => sgr(0);
29
+
30
+ /** @type {(code: number) => SGR} */
31
+ const x16ToX8 =
32
+ open === fgOpen ?
33
+ code => code >= 60 ? style.bold.combine(sgr(open + code - 60)) : sgr(open + code) :
34
+ code => code >= 60 ? sgr(open + code - 60) : sgr(open + code);
35
+
36
+ return {
37
+ black : sgr(open + 0),
38
+ red : sgr(open + 1),
39
+ green : sgr(open + 2),
40
+ yellow : sgr(open + 3),
41
+ blue : sgr(open + 4),
42
+ magenta : sgr(open + 5),
43
+ cyan : sgr(open + 6),
44
+ white : sgr(open + 7),
45
+
46
+ brightBlack : bright(0),
47
+ brightRed : bright(1),
48
+ brightGreen : bright(2),
49
+ brightYellow : bright(3),
50
+ brightBlue : bright(4),
51
+ brightMagenta : bright(5),
52
+ brightCyan : bright(6),
53
+ brightWhite : bright(7),
54
+
55
+ rgb:
56
+ colorDepth >= 24 ? (r, g, b) => sgr([open + 8, 2, ...clip(r, g, b)]) :
57
+ colorDepth >= 8 ? (r, g, b) => sgr([open + 8, 5, rgbToX256(...clip(r, g, b))]) :
58
+ colorDepth >= 4 ? (r, g, b) => sgr(open + rgbToX16(...clip(r, g, b))) :
59
+ colorDepth === 3 ?
60
+ open === fgOpen ?
61
+ (r, g, b) => x16ToX8(rgbToX16(...clip(r, g, b))) :
62
+ (r, g, b) => sgr(open + rgbToX8(...clip(r, g, b))) :
63
+ () => sgr(0),
64
+
65
+ /** @type {(...args: [number] | [number, number, number]) => SGR} */
66
+ x256:
67
+ colorDepth >= 8 ?
68
+ (...args) => sgr(
69
+ [open + 8, 5, args.length === 3 ? rgbToX256(...clip(...args)) : clip(args[0])],
70
+ ) :
71
+ colorDepth >= 4 ?
72
+ (...args) => sgr(
73
+ open +
74
+ rgbToX16(...(args.length === 3 ? clip(...args) : x256ToRgb(clip(args[0])))),
75
+ ) :
76
+ colorDepth === 3 ?
77
+ open === fgOpen ?
78
+ (...args) => x16ToX8(
79
+ args.length === 3 ?
80
+ rgbToX16(...clip(...args)) :
81
+ rgbToX16(...x256ToRgb(clip(args[0]))),
82
+ ) :
83
+ (...args) => sgr(
84
+ open + rgbToX8(
85
+ ...args.length === 3 ? clip(...args) : x256ToRgb(clip(args[0])),
86
+ ),
87
+ ) :
88
+ () => sgr(0),
89
+
90
+ default: colorDepth > 1 ? `\x1b[${close}m` : "",
91
+ };
92
+ }
93
+
94
+ /** @type {Partial<Record<ColorDepth, {fg: Channel, bg: Channel}>>} */
95
+ const cache = {};
96
+
97
+ /** @type {(colorDepth: ColorDepth) => {fg: Channel, bg: Channel}} */
98
+ const makeColor = (colorDepth = 4) =>
99
+ cache[colorDepth] ?? (cache[colorDepth] = {
100
+ fg : makeChannel(colorDepth, fgOpen, fgClose),
101
+ bg : makeChannel(colorDepth, bgOpen, bgClose),
102
+ });
103
+
104
+ module.exports = {makeColor};
@@ -0,0 +1 @@
1
+ module.exports = require("./color");
@@ -0,0 +1,28 @@
1
+ export interface Channel {
2
+ black : SGR;
3
+ red : SGR;
4
+ green : SGR;
5
+ yellow : SGR;
6
+ blue : SGR;
7
+ magenta : SGR;
8
+ cyan : SGR;
9
+ white : SGR;
10
+
11
+ brightBlack : SGR;
12
+ brightRed : SGR;
13
+ brightGreen : SGR;
14
+ brightYellow : SGR;
15
+ brightBlue : SGR;
16
+ brightMagenta : SGR;
17
+ brightCyan : SGR;
18
+ brightWhite : SGR;
19
+
20
+ rgb: (r: number, g: number, b: number) => SGR;
21
+
22
+ x256: {
23
+ (code: number): SGR;
24
+ (r: number, g: number, b: number): SGR;
25
+ };
26
+
27
+ default: string,
28
+ }
@@ -0,0 +1,81 @@
1
+ /** @typedef {import("../sgr").Code} Code */
2
+
3
+ /** @type {(r: number, g: number, b: number) => number} */
4
+ function rgbToX256(r, g, b) {
5
+ if (r === g && g === b) {
6
+ // grayscale
7
+ return 232 + Math.floor(r * 24 / 256);
8
+ }
9
+
10
+ // 6x6x6 color cube
11
+ return (
12
+ 16 +
13
+ Math.floor(r * 6 / 256) * 36 +
14
+ Math.floor(g * 6 / 256) * 6 +
15
+ Math.floor(b * 6 / 256)
16
+ );
17
+ }
18
+
19
+ /** @type {(code: number) => [number, number, number]} */
20
+ function x256ToRgb(code) {
21
+ if (code >= 232) {
22
+ // grayscale
23
+ const gray = Math.round((code - 232) * 255 / 24);
24
+ return [gray, gray, gray];
25
+ }
26
+
27
+ code -= 16;
28
+ const r = Math.floor(code / 36) * 51;
29
+ const g = Math.floor((code % 36) / 6) * 51;
30
+ const b = (code % 6) * 51;
31
+ return [r, g, b];
32
+ }
33
+
34
+ /** @type {(r: number, g: number, b: number) => number} */
35
+ function rgbToX16(r, g, b) {
36
+ // First calculate it as a 4x4x4 cube.
37
+
38
+ r = Math.min(2, Math.floor(r / 85));
39
+ g = Math.min(2, Math.floor(g / 85));
40
+ b = Math.min(2, Math.floor(b / 85));
41
+
42
+ // Which channels are "on" determines the hue (ANSI color index 0–7).
43
+ const index = (r ? 1 : 0) | (g ? 2 : 0) | (b ? 4 : 0);
44
+
45
+ // The bright bit is derived from the max channel level across all three.
46
+ const twos = (r === 2 ? 1 : 0) + (g === 2 ? 1 : 0) + (b === 2 ? 1 : 0);
47
+ const ones = (r === 1 ? 1 : 0) + (g === 1 ? 1 : 0) + (b === 1 ? 1 : 0);
48
+ const bright = twos > 0 && twos >= ones;
49
+
50
+ // Return value uses the SGR offset scheme: 0–7 for normal, 60–67 for bright.
51
+ return index + (bright ? 60 : 0);
52
+ }
53
+
54
+ /** @type {(r: number, g: number, b: number) => number} */
55
+ function rgbToX8(r, g, b) {
56
+ r = r >= 128 ? 1 : 0;
57
+ g = g >= 128 ? 1 : 0;
58
+ b = b >= 128 ? 1 : 0;
59
+
60
+ return (r ? 1 : 0) | (g ? 2 : 0) | (b ? 4 : 0);
61
+ }
62
+
63
+ /**
64
+ * @overload
65
+ * @param {number} x
66
+ * @returns {number}
67
+ * @overload
68
+ * @param {number} r
69
+ * @param {number} g
70
+ * @param {number} b
71
+ * @returns {[number, number, number]}
72
+ *//** @param {[number] | [number, number, number]} args */
73
+ const clip = (...args) => args.length === 1 ?
74
+ Math.max(0, Math.min(255, Math.floor(args[0]))) :
75
+ [
76
+ Math.max(0, Math.min(255, Math.floor(args[0]))),
77
+ Math.max(0, Math.min(255, Math.floor(args[1]))),
78
+ Math.max(0, Math.min(255, Math.floor(args[2]))),
79
+ ];
80
+
81
+ module.exports = {rgbToX256, x256ToRgb, rgbToX16, rgbToX8, clip};
@@ -0,0 +1,5 @@
1
+ module.exports = {
2
+ ...require("./reset"),
3
+ ...require("./style"),
4
+ ...require("./color"),
5
+ };
@@ -0,0 +1,3 @@
1
+ const makeReset = (enabled = true) => enabled ? "\x1b[0m" : "";
2
+
3
+ module.exports = {makeReset};
package/src/sgr/sgr.js ADDED
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+
3
+ /**
4
+ @typedef {number | number[]} Code
5
+ @typedef {{
6
+ (text: string): string;
7
+ open : string;
8
+ close : string;
9
+ codes : {open: Code, close: Code};
10
+ combine : (...styles: SGR[]) => SGR;
11
+ }} SGR
12
+ */
13
+
14
+ /** @type {(code: Code, codes: Code[]) => Code} */
15
+ function combine(code, codes, ) {
16
+ if (!codes.length)
17
+ return code;
18
+
19
+ const rtn = Array.isArray(code) ? [...code] : [code];
20
+
21
+ for (const c of codes) {
22
+ if (Array.isArray(c))
23
+ rtn.push(...c);
24
+ else
25
+ rtn.push(c);
26
+ }
27
+
28
+ return rtn;
29
+ }
30
+
31
+ /** @type {(code: Code) => string | number} */
32
+ const codeSequence = code => Array.isArray(code) ? code.join(';') : code;
33
+
34
+ /** @type {(open: Code, close: Code, reset?: boolean) => SGR} */
35
+ function sgr(open, close, reset = false) {
36
+ const openCode = codeSequence(open);
37
+ const closeCode = codeSequence(close);
38
+ const openSequence = `\x1b[${openCode}m`;
39
+ const closeSequence = `\x1b[${closeCode}m`;
40
+ const reopenCode = reset ? `${closeCode};${openCode}` : openCode;
41
+
42
+ const rxClose = new RegExp(
43
+ `(?<start>\\x1b\\[(?:\\d+;)*)${closeCode}(?<end>(?:;\\d+)*m)`,
44
+ "g"
45
+ );
46
+ const replace = `$<start>${reopenCode}$<end>`;
47
+
48
+ /** @type {(text: string) => string} */
49
+ const func = s => openSequence + s.replace(rxClose, replace) + closeSequence;
50
+
51
+ return Object.assign(func, {
52
+ codes : {open, close},
53
+ open : openSequence,
54
+ close : closeSequence,
55
+
56
+ /** @type {(...styles: SGR[]) => SGR} */
57
+ combine: (...styles) => sgr(
58
+ combine(open, styles.map(s => s.codes.open)),
59
+ combine(close, styles.map(s => s.codes.close)),
60
+ ),
61
+ });
62
+ };
63
+
64
+ /** @type {SGR} */
65
+ const disabledSGR = Object.assign(/** @type {(text: string) => string} */ s => s, {
66
+ codes : {open: 0, close: 0},
67
+ open : "",
68
+ close : "",
69
+ combine : () => disabledSGR,
70
+ });
71
+
72
+ /** @type {typeof sgr} */
73
+ const disabled = () => disabledSGR;
74
+
75
+ const makeSGR = (enabled = true) => enabled ? sgr : disabled;
76
+
77
+ module.exports = {makeSGR};
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+
3
+ const {makeSGR} = require("./sgr");
4
+
5
+ function makeStyle(enabled = true) {
6
+ const sgr = makeSGR(enabled);
7
+
8
+ return {
9
+ bold : sgr(1, 22, true),
10
+ dim : sgr(2, 22, true),
11
+ italic : sgr(3, 23),
12
+ underline : sgr(4, 24),
13
+ inverse : sgr(7, 27),
14
+ hidden : sgr(8, 28),
15
+ strikethrough : sgr(9, 29),
16
+ doubleUnderline : sgr(21, 24),
17
+ frame : sgr(51, 54),
18
+ encircle : sgr(52, 54),
19
+ overline : sgr(53, 55),
20
+ };
21
+ }
22
+
23
+ module.exports = {makeStyle};
@@ -0,0 +1,20 @@
1
+ /** @typedef {number | number[]} Code */
2
+
3
+ /** @type {(code: Code, codes: Code[]) => Code} */
4
+ function combine(code, codes, ) {
5
+ if (!codes.length)
6
+ return code;
7
+
8
+ const rtn = Array.isArray(code) ? [...code] : [code];
9
+
10
+ for (const c of codes) {
11
+ if (Array.isArray(c))
12
+ rtn.push(...c);
13
+ else
14
+ rtn.push(c);
15
+ }
16
+
17
+ return rtn;
18
+ }
19
+
20
+ module.exports = {combine};
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+
3
+ /** @typedef {import("./types").BuildAtom} BuildAtom */
4
+
5
+ /** @type {BuildAtom} */
6
+ function buildExtendedColorAtom(attribute, codes, index) {
7
+ switch (codes[index + 1]) {
8
+ case 2:
9
+ if (index + 5 >= codes.length)
10
+ return {attribute, code: codes.slice(index), skip: codes.length - index - 1};
11
+ return {attribute, code: codes.slice(index, index + 5), skip: 4};
12
+ case 5:
13
+ if (index + 3 >= codes.length)
14
+ return {attribute, code: codes.slice(index), skip: codes.length - index - 1};
15
+ return {attribute, code: codes.slice(index, index + 3), skip: 2};
16
+ default:
17
+ return {attribute, code: codes.slice(index, index + 2), skip: 1};
18
+ }
19
+ }
20
+
21
+ /** @type {BuildAtom} */
22
+ function buildIntensityAtom(attribute, codes, index, state) {
23
+ const code = codes[index];
24
+ if (code === 22)
25
+ return {attribute, code: [code]};
26
+
27
+ const current = state.get("intensity");
28
+ if (!current || current.length == 1 && current[0] === code || current[0] === 22)
29
+ return {attribute, code: [code]};
30
+
31
+ return {attribute, code: [1, 2]};
32
+ }
33
+
34
+ module.exports = {buildExtendedColorAtom, buildIntensityAtom};
@@ -0,0 +1 @@
1
+ module.exports = require("./simplify");
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+
3
+ const {buildExtendedColorAtom, buildIntensityAtom} = require("./atom");
4
+
5
+ /** @typedef {import("./types").Atom} Atom */
6
+ /** @typedef {import("./types").Attribute} Attribute */
7
+ /** @typedef {import("./types").AttributeMap} AttributeMap */
8
+ /** @typedef {import("./types").BuildAtom} BuildAtom */
9
+
10
+ /** @type {Record<number, [attribute: Attribute, buildAtom?: BuildAtom]>} */
11
+ const codeMap = {
12
+ 0 : ["reset"],
13
+ 1 : ["intensity", buildIntensityAtom],
14
+ 2 : ["intensity", buildIntensityAtom],
15
+ 3 : ["italic"],
16
+ 4 : ["underline"],
17
+ 7 : ["inverse"],
18
+ 8 : ["hidden"],
19
+ 9 : ["strikethrough"],
20
+ 21 : ["underline"],
21
+ 22 : ["intensity", buildIntensityAtom],
22
+ 23 : ["italic"],
23
+ 24 : ["underline"],
24
+ 27 : ["inverse"],
25
+ 28 : ["hidden"],
26
+ 29 : ["strikethrough"],
27
+ 30 : ["fg"],
28
+ 31 : ["fg"],
29
+ 32 : ["fg"],
30
+ 33 : ["fg"],
31
+ 34 : ["fg"],
32
+ 35 : ["fg"],
33
+ 36 : ["fg"],
34
+ 37 : ["fg"],
35
+ 38 : ["fg", buildExtendedColorAtom],
36
+ 39 : ["fg"],
37
+ 40 : ["bg"],
38
+ 41 : ["bg"],
39
+ 42 : ["bg"],
40
+ 43 : ["bg"],
41
+ 44 : ["bg"],
42
+ 45 : ["bg"],
43
+ 46 : ["bg"],
44
+ 47 : ["bg"],
45
+ 48 : ["bg", buildExtendedColorAtom],
46
+ 49 : ["bg"],
47
+ 51 : ["frame"],
48
+ 52 : ["frame"],
49
+ 53 : ["overline"],
50
+ 54 : ["frame"],
51
+ 55 : ["overline"],
52
+ 90 : ["fg"],
53
+ 91 : ["fg"],
54
+ 92 : ["fg"],
55
+ 93 : ["fg"],
56
+ 94 : ["fg"],
57
+ 95 : ["fg"],
58
+ 96 : ["fg"],
59
+ 97 : ["fg"],
60
+ 100 : ["bg"],
61
+ 101 : ["bg"],
62
+ 102 : ["bg"],
63
+ 103 : ["bg"],
64
+ 104 : ["bg"],
65
+ 105 : ["bg"],
66
+ 106 : ["bg"],
67
+ 107 : ["bg"],
68
+ };
69
+
70
+ /** @type {(codes: number[], index: number, state: AttributeMap) => Atom} */
71
+ function lookUpAtom(codes, index, state) {
72
+ const code = codes[index];
73
+ const entry = codeMap[code];
74
+
75
+ if (!entry)
76
+ return {attribute: code, code: [code]};
77
+
78
+ const [attribute, buildAtom] = entry;
79
+
80
+ if (buildAtom)
81
+ return buildAtom(attribute, codes, index, state);
82
+
83
+ return {attribute, code: [code]};
84
+ }
85
+
86
+ module.exports = {lookUpAtom};
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+
3
+ const {rxSGR} = require("../patterns");
4
+ const {State} = require("./state");
5
+
6
+ /** @type {(text: string) => string} */
7
+ function simplify(text) {
8
+ // `.split` doesn't modify `lastIndex`, so no clone is necessary here.
9
+ const parts = text.split(rxSGR);
10
+
11
+ const state = new State();
12
+ let result = parts[0];
13
+
14
+ for (let i = 1; i < parts.length; ) {
15
+ let codes = parts[i++].split(";").map(Number);
16
+ let chunk = parts[i++];
17
+
18
+ while (!chunk && i < parts.length) {
19
+ // Merge consecutive SGR sequences.
20
+ codes.push(...parts[i++].split(";").map(Number));
21
+ chunk = parts[i++];
22
+ }
23
+
24
+ codes = state.update(codes);
25
+ if (codes.length)
26
+ result += `\x1b[${codes.join(";")}m`;
27
+ result += chunk;
28
+ }
29
+
30
+ return result;
31
+ }
32
+
33
+ module.exports = {simplify};
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+
3
+ const {lookUpAtom} = require("./lookup");
4
+ const {arraysEqual, transitionIntensity} = require("./utils");
5
+
6
+ /** @typedef {import("./types").Attribute} Attribute */
7
+ /** @typedef {import("./types").AttributeMap} AttributeMap */
8
+
9
+ /** @type {Map<Attribute, number[]>} */
10
+ const defaultCodes = new Map([
11
+ ["intensity", [22]],
12
+ ["italic", [23]],
13
+ ["underline", [24]],
14
+ ["inverse", [27]],
15
+ ["hidden", [28]],
16
+ ["strikethrough",[29]],
17
+ ["fg", [39]],
18
+ ["bg", [49]],
19
+ ["frame", [54]],
20
+ ["overline", [55]],
21
+ ]);
22
+
23
+ /** @type {AttributeMap} */
24
+ class State extends Map {
25
+ /** @type {(codes: number[]) => number[]} */
26
+ update(codes) {
27
+ /** @type {AttributeMap} */
28
+ let snapshot = new Map(this);
29
+
30
+ /** @type {Set<Attribute>} */
31
+ const attributes = new Set();
32
+
33
+ for (let i = 0; i < codes.length; i++) {
34
+ const {attribute, code, skip} = lookUpAtom(codes, i, this);
35
+
36
+ if (attribute === "reset") {
37
+ attributes.clear();
38
+ this.clear();
39
+ attributes.add("reset");
40
+ for (const [attr, defaultCode] of defaultCodes)
41
+ this.set(attr, defaultCode);
42
+
43
+ snapshot = new Map(this);
44
+ continue;
45
+ }
46
+
47
+ attributes.add(attribute);
48
+ this.set(attribute, code);
49
+
50
+ if (skip)
51
+ i += skip;
52
+ }
53
+
54
+ return Array.from(attributes).flatMap(attribute => {
55
+ if (attribute === "reset")
56
+ return [0];
57
+
58
+ const code = this.get(attribute);
59
+
60
+ if (arraysEqual(code, snapshot.get(attribute)))
61
+ return [];
62
+
63
+ if (attribute === "intensity") {
64
+ const previous = snapshot.get(attribute);
65
+ return transitionIntensity(previous, code);
66
+ }
67
+
68
+ return code;
69
+ });
70
+ }
71
+ }
72
+
73
+ module.exports = {State};
@@ -0,0 +1,28 @@
1
+ export type Attribute =
2
+ | "intensity"
3
+ | "italic"
4
+ | "underline"
5
+ | "inverse"
6
+ | "hidden"
7
+ | "strikethrough"
8
+ | "fg"
9
+ | "bg"
10
+ | "frame"
11
+ | "overline"
12
+ | "reset"
13
+ | number;
14
+
15
+ type AttributeMap = Map<Attribute, number[]>;
16
+
17
+ export interface Atom {
18
+ attribute : Attribute;
19
+ code : number[];
20
+ skip? : number;
21
+ }
22
+
23
+ export type BuildAtom = (
24
+ attribute: Attribute,
25
+ codes : number[],
26
+ index : number,
27
+ state : AttributeMap,
28
+ ) => Atom;
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+
3
+ /** @type {<T>(a: T[] | undefined, b: T[] | undefined) => boolean} */
4
+ function arraysEqual(a, b) {
5
+ if (a === b)
6
+ return true;
7
+
8
+ if (!a || !b || a.length !== b.length)
9
+ return false;
10
+
11
+ for (let i = 0; i < a.length; i++) {
12
+ if (a[i] !== b[i])
13
+ return false;
14
+ }
15
+
16
+ return true;
17
+ }
18
+
19
+ /** @type {(previous: number[] | undefined, current: number[]) => number[]} */
20
+ function transitionIntensity(previous, current) {
21
+ if (!previous || previous[0] === 22 || current[0] === 22)
22
+ return current;
23
+
24
+ const oldBold = previous[0] === 1;
25
+ const newBold = current[0] === 1;
26
+ const oldDim = previous[previous.length - 1] === 2;
27
+ const newDim = current[current.length - 1] === 2;
28
+
29
+ const result = [];
30
+
31
+ if (oldBold && !newBold || oldDim && !newDim)
32
+ result.push(22);
33
+ if (newBold && (!oldBold || result[0] === 22))
34
+ result.push(1);
35
+ if (newDim && (!oldDim || result[0] === 22))
36
+ result.push(2);
37
+
38
+ return result;
39
+ }
40
+
41
+ module.exports = {arraysEqual, transitionIntensity};