@peteanderson/ansi 0.2.1 → 0.2.2
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 +7 -2
- package/dist/ansi.d.ts +2 -1
- package/dist/ansi.js +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/simplify/atom.d.ts +3 -0
- package/dist/simplify/atom.js +28 -0
- package/dist/simplify/index.d.ts +2 -0
- package/dist/simplify/index.js +2 -0
- package/dist/simplify/lookup.d.ts +5 -0
- package/dist/simplify/lookup.js +77 -0
- package/dist/simplify/simplify.d.ts +1 -0
- package/dist/simplify/simplify.js +24 -0
- package/dist/simplify/state.d.ts +10 -0
- package/dist/simplify/state.js +57 -0
- package/dist/simplify/utils.d.ts +2 -0
- package/dist/simplify/utils.js +31 -0
- package/package.json +1 -1
- package/src/ansi.js +2 -0
- package/src/simplify/atom.js +34 -0
- package/src/simplify/index.js +1 -0
- package/src/simplify/lookup.js +86 -0
- package/src/simplify/simplify.js +33 -0
- package/src/simplify/state.js +73 -0
- package/src/simplify/types.d.ts +28 -0
- package/src/simplify/utils.js +41 -0
package/README.md
CHANGED
|
@@ -19,6 +19,7 @@ console.log(ansi.style.bold("important"));
|
|
|
19
19
|
console.log(ansi.fg.rgb(5, 2, 0)("custom color"));
|
|
20
20
|
console.log(ansi.strip("styled text")); // remove all ANSI codes
|
|
21
21
|
console.log(ansi.slice("styled text", 0, 5)); // slice by visible length
|
|
22
|
+
console.log(ansi.simplify(styledText)); // combine adjacent SGR sequences
|
|
22
23
|
```
|
|
23
24
|
|
|
24
25
|
## API
|
|
@@ -52,8 +53,7 @@ Functions that wrap text with style codes:
|
|
|
52
53
|
`bold` `dim` `italic` `underline` `inverse` `hidden` `strikethrough` `doubleUnderline` `framed`
|
|
53
54
|
`encircled` `overline`
|
|
54
55
|
|
|
55
|
-
All return functions with `open` and `close` properties for raw sequences.
|
|
56
|
-
`combine()` method for appending additional styles.
|
|
56
|
+
All return functions with `open` and `close` properties for raw sequences.
|
|
57
57
|
|
|
58
58
|
### `caret`
|
|
59
59
|
|
|
@@ -90,6 +90,11 @@ but counts only visible characters.
|
|
|
90
90
|
|
|
91
91
|
Removes "unsafe" CSI sequences (non-SGR), leaving only color and style codes.
|
|
92
92
|
|
|
93
|
+
### `simplify(text)`
|
|
94
|
+
|
|
95
|
+
Simplifies ANSI SGR sequences by combining adjacent sequences and removing redundant codes. Useful
|
|
96
|
+
for optimizing styled strings.
|
|
97
|
+
|
|
93
98
|
## License
|
|
94
99
|
|
|
95
100
|
MIT
|
package/dist/ansi.d.ts
CHANGED
|
@@ -8,4 +8,5 @@ import { bg } from "./sgr/color";
|
|
|
8
8
|
import { strip } from "./strip";
|
|
9
9
|
import { sanitize } from "./strip";
|
|
10
10
|
import { slice } from "./strip";
|
|
11
|
-
|
|
11
|
+
import { simplify } from "./simplify/simplify";
|
|
12
|
+
export { caret, erase, scroll, reset, style, fg, bg, strip, sanitize, slice, simplify };
|
package/dist/ansi.js
CHANGED
|
@@ -3,6 +3,7 @@ const caret = require("./caret");
|
|
|
3
3
|
const erase = require("./erase");
|
|
4
4
|
const scroll = require("./scroll");
|
|
5
5
|
const { bg, fg, reset, style } = require("./sgr");
|
|
6
|
+
const { simplify } = require("./simplify");
|
|
6
7
|
const { sanitize, slice, strip } = require("./strip");
|
|
7
8
|
const ansi = {
|
|
8
9
|
caret,
|
|
@@ -15,5 +16,6 @@ const ansi = {
|
|
|
15
16
|
strip,
|
|
16
17
|
sanitize,
|
|
17
18
|
slice,
|
|
19
|
+
simplify,
|
|
18
20
|
};
|
|
19
21
|
module.exports = ansi;
|
package/dist/index.d.ts
CHANGED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export type BuildAtom = import("./types").BuildAtom;
|
|
2
|
+
export function buildExtendedColorAtom(attribute: import("./types").Attribute, codes: number[], index: number, state: import("./types").AttributeMap): import("./types").Atom;
|
|
3
|
+
export function buildIntensityAtom(attribute: import("./types").Attribute, codes: number[], index: number, state: import("./types").AttributeMap): import("./types").Atom;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/** @typedef {import("./types").BuildAtom} BuildAtom */
|
|
3
|
+
/** @type {BuildAtom} */
|
|
4
|
+
function buildExtendedColorAtom(attribute, codes, index) {
|
|
5
|
+
switch (codes[index + 1]) {
|
|
6
|
+
case 2:
|
|
7
|
+
if (index + 5 >= codes.length)
|
|
8
|
+
return { attribute, code: codes.slice(index), skip: codes.length - index - 1 };
|
|
9
|
+
return { attribute, code: codes.slice(index, index + 5), skip: 4 };
|
|
10
|
+
case 5:
|
|
11
|
+
if (index + 3 >= codes.length)
|
|
12
|
+
return { attribute, code: codes.slice(index), skip: codes.length - index - 1 };
|
|
13
|
+
return { attribute, code: codes.slice(index, index + 3), skip: 2 };
|
|
14
|
+
default:
|
|
15
|
+
return { attribute, code: codes.slice(index, index + 2), skip: 1 };
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/** @type {BuildAtom} */
|
|
19
|
+
function buildIntensityAtom(attribute, codes, index, state) {
|
|
20
|
+
const code = codes[index];
|
|
21
|
+
if (code === 22)
|
|
22
|
+
return { attribute, code: [code] };
|
|
23
|
+
const current = state.get("intensity");
|
|
24
|
+
if (!current || current.length == 1 && current[0] === code || current[0] === 22)
|
|
25
|
+
return { attribute, code: [code] };
|
|
26
|
+
return { attribute, code: [1, 2] };
|
|
27
|
+
}
|
|
28
|
+
module.exports = { buildExtendedColorAtom, buildIntensityAtom };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type Atom = import("./types").Atom;
|
|
2
|
+
export type Attribute = import("./types").Attribute;
|
|
3
|
+
export type AttributeMap = import("./types").AttributeMap;
|
|
4
|
+
export type BuildAtom = import("./types").BuildAtom;
|
|
5
|
+
export function lookUpAtom(codes: number[], index: number, state: AttributeMap): Atom;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const { buildExtendedColorAtom, buildIntensityAtom } = require("./atom");
|
|
3
|
+
/** @typedef {import("./types").Atom} Atom */
|
|
4
|
+
/** @typedef {import("./types").Attribute} Attribute */
|
|
5
|
+
/** @typedef {import("./types").AttributeMap} AttributeMap */
|
|
6
|
+
/** @typedef {import("./types").BuildAtom} BuildAtom */
|
|
7
|
+
/** @type {Record<number, [attribute: Attribute, buildAtom?: BuildAtom]>} */
|
|
8
|
+
const codeMap = {
|
|
9
|
+
0: ["reset"],
|
|
10
|
+
1: ["intensity", buildIntensityAtom],
|
|
11
|
+
2: ["intensity", buildIntensityAtom],
|
|
12
|
+
3: ["italic"],
|
|
13
|
+
4: ["underline"],
|
|
14
|
+
7: ["inverse"],
|
|
15
|
+
8: ["hidden"],
|
|
16
|
+
9: ["strikethrough"],
|
|
17
|
+
21: ["underline"],
|
|
18
|
+
22: ["intensity", buildIntensityAtom],
|
|
19
|
+
23: ["italic"],
|
|
20
|
+
24: ["underline"],
|
|
21
|
+
27: ["inverse"],
|
|
22
|
+
28: ["hidden"],
|
|
23
|
+
29: ["strikethrough"],
|
|
24
|
+
30: ["fg"],
|
|
25
|
+
31: ["fg"],
|
|
26
|
+
32: ["fg"],
|
|
27
|
+
33: ["fg"],
|
|
28
|
+
34: ["fg"],
|
|
29
|
+
35: ["fg"],
|
|
30
|
+
36: ["fg"],
|
|
31
|
+
37: ["fg"],
|
|
32
|
+
38: ["fg", buildExtendedColorAtom],
|
|
33
|
+
39: ["fg"],
|
|
34
|
+
40: ["bg"],
|
|
35
|
+
41: ["bg"],
|
|
36
|
+
42: ["bg"],
|
|
37
|
+
43: ["bg"],
|
|
38
|
+
44: ["bg"],
|
|
39
|
+
45: ["bg"],
|
|
40
|
+
46: ["bg"],
|
|
41
|
+
47: ["bg"],
|
|
42
|
+
48: ["bg", buildExtendedColorAtom],
|
|
43
|
+
49: ["bg"],
|
|
44
|
+
51: ["frame"],
|
|
45
|
+
52: ["frame"],
|
|
46
|
+
53: ["overline"],
|
|
47
|
+
54: ["frame"],
|
|
48
|
+
55: ["overline"],
|
|
49
|
+
90: ["fg"],
|
|
50
|
+
91: ["fg"],
|
|
51
|
+
92: ["fg"],
|
|
52
|
+
93: ["fg"],
|
|
53
|
+
94: ["fg"],
|
|
54
|
+
95: ["fg"],
|
|
55
|
+
96: ["fg"],
|
|
56
|
+
97: ["fg"],
|
|
57
|
+
100: ["bg"],
|
|
58
|
+
101: ["bg"],
|
|
59
|
+
102: ["bg"],
|
|
60
|
+
103: ["bg"],
|
|
61
|
+
104: ["bg"],
|
|
62
|
+
105: ["bg"],
|
|
63
|
+
106: ["bg"],
|
|
64
|
+
107: ["bg"],
|
|
65
|
+
};
|
|
66
|
+
/** @type {(codes: number[], index: number, state: AttributeMap) => Atom} */
|
|
67
|
+
function lookUpAtom(codes, index, state) {
|
|
68
|
+
const code = codes[index];
|
|
69
|
+
const entry = codeMap[code];
|
|
70
|
+
if (!entry)
|
|
71
|
+
return { attribute: code, code: [code] };
|
|
72
|
+
const [attribute, buildAtom] = entry;
|
|
73
|
+
if (buildAtom)
|
|
74
|
+
return buildAtom(attribute, codes, index, state);
|
|
75
|
+
return { attribute, code: [code] };
|
|
76
|
+
}
|
|
77
|
+
module.exports = { lookUpAtom };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function simplify(text: string): string;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const { State } = require("./state");
|
|
3
|
+
const rxSGR = /\x1b\[([0-9;]+)m/;
|
|
4
|
+
/** @type {(text: string) => string} */
|
|
5
|
+
function simplify(text) {
|
|
6
|
+
const parts = text.split(rxSGR);
|
|
7
|
+
const state = new State();
|
|
8
|
+
let result = parts[0];
|
|
9
|
+
for (let i = 1; i < parts.length;) {
|
|
10
|
+
let codes = parts[i++].split(";").map(Number);
|
|
11
|
+
let chunk = parts[i++];
|
|
12
|
+
while (!chunk && i < parts.length) {
|
|
13
|
+
// Merge consecutive SGR sequences.
|
|
14
|
+
codes.push(...parts[i++].split(";").map(Number));
|
|
15
|
+
chunk = parts[i++];
|
|
16
|
+
}
|
|
17
|
+
codes = state.update(codes);
|
|
18
|
+
if (codes.length)
|
|
19
|
+
result += `\x1b[${codes.join(";")}m`;
|
|
20
|
+
result += chunk;
|
|
21
|
+
}
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
24
|
+
module.exports = { simplify };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type Attribute = import("./types").Attribute;
|
|
2
|
+
export type AttributeMap = import("./types").AttributeMap;
|
|
3
|
+
/** @type {AttributeMap} */
|
|
4
|
+
export class State extends Map<any, any> {
|
|
5
|
+
constructor();
|
|
6
|
+
constructor(entries?: readonly (readonly [any, any])[] | null | undefined);
|
|
7
|
+
constructor();
|
|
8
|
+
constructor(iterable?: Iterable<readonly [any, any]> | null | undefined);
|
|
9
|
+
update(codes: number[]): number[];
|
|
10
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const { lookUpAtom } = require("./lookup");
|
|
3
|
+
const { arraysEqual, transitionIntensity } = require("./utils");
|
|
4
|
+
/** @typedef {import("./types").Attribute} Attribute */
|
|
5
|
+
/** @typedef {import("./types").AttributeMap} AttributeMap */
|
|
6
|
+
/** @type {Map<Attribute, number[]>} */
|
|
7
|
+
const defaultCodes = new Map([
|
|
8
|
+
["intensity", [22]],
|
|
9
|
+
["italic", [23]],
|
|
10
|
+
["underline", [24]],
|
|
11
|
+
["inverse", [27]],
|
|
12
|
+
["hidden", [28]],
|
|
13
|
+
["strikethrough", [29]],
|
|
14
|
+
["fg", [39]],
|
|
15
|
+
["bg", [49]],
|
|
16
|
+
["frame", [54]],
|
|
17
|
+
["overline", [55]],
|
|
18
|
+
]);
|
|
19
|
+
/** @type {AttributeMap} */
|
|
20
|
+
class State extends Map {
|
|
21
|
+
/** @type {(codes: number[]) => number[]} */
|
|
22
|
+
update(codes) {
|
|
23
|
+
/** @type {AttributeMap} */
|
|
24
|
+
let snapshot = new Map(this);
|
|
25
|
+
/** @type {Set<Attribute>} */
|
|
26
|
+
const attributes = new Set();
|
|
27
|
+
for (let i = 0; i < codes.length; i++) {
|
|
28
|
+
const { attribute, code, skip } = lookUpAtom(codes, i, this);
|
|
29
|
+
if (attribute === "reset") {
|
|
30
|
+
attributes.clear();
|
|
31
|
+
this.clear();
|
|
32
|
+
attributes.add("reset");
|
|
33
|
+
for (const [attr, defaultCode] of defaultCodes)
|
|
34
|
+
this.set(attr, defaultCode);
|
|
35
|
+
snapshot = new Map(this);
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
attributes.add(attribute);
|
|
39
|
+
this.set(attribute, code);
|
|
40
|
+
if (skip)
|
|
41
|
+
i += skip;
|
|
42
|
+
}
|
|
43
|
+
return Array.from(attributes).flatMap(attribute => {
|
|
44
|
+
if (attribute === "reset")
|
|
45
|
+
return [0];
|
|
46
|
+
const code = this.get(attribute);
|
|
47
|
+
if (arraysEqual(code, snapshot.get(attribute)))
|
|
48
|
+
return [];
|
|
49
|
+
if (attribute === "intensity") {
|
|
50
|
+
const previous = snapshot.get(attribute);
|
|
51
|
+
return transitionIntensity(previous, code);
|
|
52
|
+
}
|
|
53
|
+
return code;
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
module.exports = { State };
|
|
@@ -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 };
|
package/package.json
CHANGED
package/src/ansi.js
CHANGED
|
@@ -4,6 +4,7 @@ const caret = require("./caret");
|
|
|
4
4
|
const erase = require("./erase");
|
|
5
5
|
const scroll = require("./scroll");
|
|
6
6
|
const {bg, fg, reset, style} = require("./sgr");
|
|
7
|
+
const {simplify} = require("./simplify");
|
|
7
8
|
const {sanitize, slice, strip} = require("./strip");
|
|
8
9
|
|
|
9
10
|
const ansi = {
|
|
@@ -19,6 +20,7 @@ const ansi = {
|
|
|
19
20
|
strip,
|
|
20
21
|
sanitize,
|
|
21
22
|
slice,
|
|
23
|
+
simplify,
|
|
22
24
|
};
|
|
23
25
|
|
|
24
26
|
module.exports = ansi;
|
|
@@ -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 {State} = require("./state");
|
|
4
|
+
|
|
5
|
+
const rxSGR = /\x1b\[([0-9;]+)m/;
|
|
6
|
+
|
|
7
|
+
/** @type {(text: string) => string} */
|
|
8
|
+
function simplify(text) {
|
|
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};
|