@ansi-tools/parser 0.0.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.
- package/README.md +187 -0
- package/dist/escaped.d.ts +7 -0
- package/dist/escaped.js +203 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +3 -0
- package/dist/parse-BirjVUvQ.d.ts +64 -0
- package/dist/parse-ClmKWMZx.js +485 -0
- package/package.json +40 -0
- package/src/constants.ts +40 -0
- package/src/escaped.ts +4 -0
- package/src/index.ts +4 -0
- package/src/parse.escaped.test.ts +86 -0
- package/src/parse.test.ts +86 -0
- package/src/parse.ts +111 -0
- package/src/parsers/csi.test.ts +55 -0
- package/src/parsers/csi.ts +54 -0
- package/src/parsers/dcs.test.ts +47 -0
- package/src/parsers/dcs.ts +36 -0
- package/src/parsers/dec.test.ts +24 -0
- package/src/parsers/dec.ts +30 -0
- package/src/parsers/esc.test.ts +19 -0
- package/src/parsers/esc.ts +6 -0
- package/src/parsers/osc.test.ts +36 -0
- package/src/parsers/osc.ts +29 -0
- package/src/tokenize.escaped.test.ts +410 -0
- package/src/tokenize.escaped.ts +191 -0
- package/src/tokenize.test.ts +118 -0
- package/src/tokenize.ts +140 -0
- package/src/types.ts +24 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { tokenizer } from "./tokenize.ts";
|
|
4
|
+
import { parser } from "./parse.ts";
|
|
5
|
+
import { CODE_TYPES } from "./constants.ts";
|
|
6
|
+
|
|
7
|
+
test("parse simple text", () => {
|
|
8
|
+
const input = "hello world";
|
|
9
|
+
const tokens = tokenizer(input);
|
|
10
|
+
const codes = [...parser(tokens)];
|
|
11
|
+
assert.deepEqual(codes, [{ type: CODE_TYPES.TEXT, pos: 0, raw: "hello world" }]);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("parse mixed text and csi", () => {
|
|
15
|
+
const input = `hello \x1b[31mworld\x1b[0m`;
|
|
16
|
+
const tokens = tokenizer(input);
|
|
17
|
+
const codes = [...parser(tokens)];
|
|
18
|
+
assert.deepEqual(codes, [
|
|
19
|
+
{ type: CODE_TYPES.TEXT, pos: 0, raw: "hello " },
|
|
20
|
+
{ type: CODE_TYPES.CSI, pos: 6, raw: "\x1b[31m", params: ["31"], command: "m" },
|
|
21
|
+
{ type: CODE_TYPES.TEXT, pos: 11, raw: "world" },
|
|
22
|
+
{ type: CODE_TYPES.CSI, pos: 16, raw: "\x1b[0m", params: ["0"], command: "m" },
|
|
23
|
+
]);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("subsequent escape sequences", () => {
|
|
27
|
+
const input = `\x1b[31m\x1b[32m\x1b[33m`;
|
|
28
|
+
const tokens = tokenizer(input);
|
|
29
|
+
const codes = [...parser(tokens)];
|
|
30
|
+
assert.deepEqual(codes, [
|
|
31
|
+
{ type: CODE_TYPES.CSI, pos: 0, raw: "\x1b[31m", params: ["31"], command: "m" },
|
|
32
|
+
{ type: CODE_TYPES.CSI, pos: 5, raw: "\x1b[32m", params: ["32"], command: "m" },
|
|
33
|
+
{ type: CODE_TYPES.CSI, pos: 10, raw: "\x1b[33m", params: ["33"], command: "m" },
|
|
34
|
+
]);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("parse multiple different sequence types", () => {
|
|
38
|
+
const input = `\x1b[1;32m\x1b]0;title\x07\x1b=\x1bPdata\x1b\\`;
|
|
39
|
+
const tokens = tokenizer(input);
|
|
40
|
+
const codes = [...parser(tokens)];
|
|
41
|
+
assert.deepEqual(codes, [
|
|
42
|
+
{ type: CODE_TYPES.CSI, pos: 0, raw: "\x1b[1;32m", params: ["1", "32"], command: "m" },
|
|
43
|
+
{ type: CODE_TYPES.OSC, pos: 7, raw: "\x1b]0;title\x07", params: ["title"], command: "0" },
|
|
44
|
+
{ type: CODE_TYPES.ESC, pos: 17, raw: "\x1b=", command: "=", params: [] },
|
|
45
|
+
{ type: CODE_TYPES.DCS, pos: 19, raw: "\x1bPdata\x1b\\", params: ["data"], command: "" },
|
|
46
|
+
]);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("parse mixed standard and private sequences", () => {
|
|
50
|
+
const input = `\x1b[31m\x1b[<5h\x1b[?25l\x1b[>c`;
|
|
51
|
+
const tokens = tokenizer(input);
|
|
52
|
+
const codes = [...parser(tokens)];
|
|
53
|
+
assert.deepEqual(codes, [
|
|
54
|
+
{ type: CODE_TYPES.CSI, pos: 0, raw: "\x1b[31m", params: ["31"], command: "m" },
|
|
55
|
+
{ type: CODE_TYPES.PRIVATE, pos: 5, raw: "\x1b[<5h", params: ["5"], command: "<h" },
|
|
56
|
+
{ type: CODE_TYPES.DEC, pos: 10, raw: "\x1b[?25l", params: ["25"], command: "l" },
|
|
57
|
+
{ type: CODE_TYPES.PRIVATE, pos: 16, raw: "\x1b[>c", params: [], command: ">c" },
|
|
58
|
+
]);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("parse complex missing parameter scenarios", () => {
|
|
62
|
+
const input = `\x1b[;5;m\x1b[?;h\x1bP$q;;\x1b\\`;
|
|
63
|
+
const tokens = tokenizer(input);
|
|
64
|
+
const codes = [...parser(tokens)];
|
|
65
|
+
assert.deepEqual(codes, [
|
|
66
|
+
{ type: CODE_TYPES.CSI, pos: 0, raw: "\x1b[;5;m", params: ["-1", "5", "-1"], command: "m" },
|
|
67
|
+
{ type: CODE_TYPES.DEC, pos: 6, raw: "\x1b[?;h", params: ["-1", "-1"], command: "h" },
|
|
68
|
+
{ type: CODE_TYPES.DCS, pos: 11, raw: "\x1bP$q;;\x1b\\", params: ["-1", "-1", "-1"], command: "$q" },
|
|
69
|
+
]);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("parse iTerm2 image sequence", () => {
|
|
73
|
+
const input = `\x1b]1337;File=inline=1;width=1;height=1:R0lG=\x07`;
|
|
74
|
+
const tokens = tokenizer(input);
|
|
75
|
+
const codes = [...parser(tokens)];
|
|
76
|
+
assert.deepEqual(codes, [
|
|
77
|
+
{ type: CODE_TYPES.OSC, pos: 0, raw: input, command: "1337", params: ["File=inline=1;width=1;height=1:R0lG="] },
|
|
78
|
+
]);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("parse DECUDK sequence", () => {
|
|
82
|
+
const input = `\x1bP0|23/68656c6c6f\x1b\\`;
|
|
83
|
+
const tokens = tokenizer(input);
|
|
84
|
+
const codes = [...parser(tokens)];
|
|
85
|
+
assert.deepEqual(codes, [{ type: CODE_TYPES.DCS, pos: 0, raw: input, command: "", params: ["0|23/68656c6c6f"] }]);
|
|
86
|
+
});
|
package/src/parse.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import {
|
|
2
|
+
APC_OPEN,
|
|
3
|
+
APC,
|
|
4
|
+
CODE_TYPES,
|
|
5
|
+
CSI,
|
|
6
|
+
DCS_OPEN,
|
|
7
|
+
DCS,
|
|
8
|
+
DEC_OPEN,
|
|
9
|
+
ESC,
|
|
10
|
+
OSC,
|
|
11
|
+
PM_OPEN,
|
|
12
|
+
PM,
|
|
13
|
+
PRIVATE_OPENERS,
|
|
14
|
+
SOS_OPEN,
|
|
15
|
+
SOS,
|
|
16
|
+
TOKEN_TYPES,
|
|
17
|
+
} from "./constants.ts";
|
|
18
|
+
import { parseCSI, parsePrivateCSI } from "./parsers/csi.ts";
|
|
19
|
+
import { parseDCS } from "./parsers/dcs.ts";
|
|
20
|
+
import { parseDEC } from "./parsers/dec.ts";
|
|
21
|
+
import { parseESC } from "./parsers/esc.ts";
|
|
22
|
+
import { parseOSC } from "./parsers/osc.ts";
|
|
23
|
+
import { tokenizer } from "./tokenize.ts";
|
|
24
|
+
import type { CODE, TOKEN } from "./types.ts";
|
|
25
|
+
|
|
26
|
+
const debug = false;
|
|
27
|
+
|
|
28
|
+
function emit(token: CODE) {
|
|
29
|
+
if (debug) console.log("code", token);
|
|
30
|
+
return token;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function* parser(tokens: Generator<TOKEN>): Generator<CODE> {
|
|
34
|
+
let current = tokens.next();
|
|
35
|
+
|
|
36
|
+
while (!current.done) {
|
|
37
|
+
const token = current.value;
|
|
38
|
+
|
|
39
|
+
if (token.type === TOKEN_TYPES.TEXT) {
|
|
40
|
+
yield emit({ type: CODE_TYPES.TEXT, pos: token.pos, raw: token.raw });
|
|
41
|
+
current = tokens.next();
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (token.type === TOKEN_TYPES.INTRODUCER) {
|
|
46
|
+
const pos = token.pos;
|
|
47
|
+
let raw = token.raw;
|
|
48
|
+
let data = "";
|
|
49
|
+
let finalToken: TOKEN | undefined;
|
|
50
|
+
|
|
51
|
+
current = tokens.next();
|
|
52
|
+
|
|
53
|
+
while (!current.done && !finalToken) {
|
|
54
|
+
const nextToken = current.value;
|
|
55
|
+
|
|
56
|
+
if (nextToken.type === TOKEN_TYPES.DATA) {
|
|
57
|
+
data += nextToken.raw;
|
|
58
|
+
raw += nextToken.raw;
|
|
59
|
+
} else if (nextToken.type === TOKEN_TYPES.FINAL) {
|
|
60
|
+
finalToken = nextToken;
|
|
61
|
+
raw += nextToken.raw;
|
|
62
|
+
}
|
|
63
|
+
current = tokens.next();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (finalToken) {
|
|
67
|
+
switch (token.code) {
|
|
68
|
+
case CSI:
|
|
69
|
+
if (data.startsWith(DEC_OPEN)) {
|
|
70
|
+
yield emit(parseDEC(pos, raw, data, finalToken.raw));
|
|
71
|
+
} else if (PRIVATE_OPENERS.has(data[0])) {
|
|
72
|
+
yield emit(parsePrivateCSI(pos, raw, data, finalToken.raw));
|
|
73
|
+
} else {
|
|
74
|
+
yield emit(parseCSI(pos, raw, data, finalToken.raw));
|
|
75
|
+
}
|
|
76
|
+
break;
|
|
77
|
+
case OSC:
|
|
78
|
+
yield emit(parseOSC(pos, raw, data));
|
|
79
|
+
break;
|
|
80
|
+
case DCS:
|
|
81
|
+
case DCS_OPEN:
|
|
82
|
+
yield emit(parseDCS(pos, raw, data));
|
|
83
|
+
break;
|
|
84
|
+
case APC:
|
|
85
|
+
case APC_OPEN:
|
|
86
|
+
yield emit({ type: CODE_TYPES.STRING, pos, raw, command: "APC", params: data ? [data] : [] });
|
|
87
|
+
break;
|
|
88
|
+
case PM:
|
|
89
|
+
case PM_OPEN:
|
|
90
|
+
yield emit({ type: CODE_TYPES.STRING, pos, raw, command: "PM", params: data ? [data] : [] });
|
|
91
|
+
break;
|
|
92
|
+
case SOS:
|
|
93
|
+
case SOS_OPEN:
|
|
94
|
+
yield emit({ type: CODE_TYPES.STRING, pos, raw, command: "SOS", params: data ? [data] : [] });
|
|
95
|
+
break;
|
|
96
|
+
case ESC:
|
|
97
|
+
yield emit(parseESC(pos, raw, finalToken.raw, data));
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
} else if (token.code === ESC) {
|
|
101
|
+
yield emit(parseESC(pos, raw, "", ""));
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
current = tokens.next();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function parse(input: string): CODE[] {
|
|
110
|
+
return Array.from(parser(tokenizer(input)));
|
|
111
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { parseCSI, parsePrivateCSI } from "./csi.ts";
|
|
4
|
+
import { CODE_TYPES } from "../constants.ts";
|
|
5
|
+
|
|
6
|
+
test("parseCSI simple command", () => {
|
|
7
|
+
const result = parseCSI(0, "\\e[31m", "31", "m");
|
|
8
|
+
assert.deepEqual(result, { type: CODE_TYPES.CSI, pos: 0, raw: "\\e[31m", params: ["31"], command: "m" });
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test("parseCSI with multiple params", () => {
|
|
12
|
+
const result = parseCSI(0, "\\e[1;31m", "1;31", "m");
|
|
13
|
+
assert.deepEqual(result, { type: CODE_TYPES.CSI, pos: 0, raw: "\\e[1;31m", params: ["1", "31"], command: "m" });
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("parseCSI with missing parameters", () => {
|
|
17
|
+
const result = parseCSI(0, "\\e[;31m", ";31", "m");
|
|
18
|
+
assert.deepEqual(result, { type: CODE_TYPES.CSI, pos: 0, raw: "\\e[;31m", params: ["-1", "31"], command: "m" });
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("parseCSI with trailing semicolon", () => {
|
|
22
|
+
const result = parseCSI(0, "\\e[31;m", "31;", "m");
|
|
23
|
+
assert.deepEqual(result, { type: CODE_TYPES.CSI, pos: 0, raw: "\\e[31;m", params: ["31", "-1"], command: "m" });
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("parseCSI with leading semicolon", () => {
|
|
27
|
+
const result = parseCSI(0, "\\e[;m", ";", "m");
|
|
28
|
+
assert.deepEqual(result, { type: CODE_TYPES.CSI, pos: 0, raw: "\\e[;m", params: ["-1", "-1"], command: "m" });
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("parseCSI no data", () => {
|
|
32
|
+
const result = parseCSI(0, "\\e[m", "", "m");
|
|
33
|
+
assert.deepEqual(result, { type: CODE_TYPES.CSI, pos: 0, raw: "\\e[m", params: [], command: "m" });
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("parsePrivateCSI with < introducer", () => {
|
|
37
|
+
const result = parsePrivateCSI(0, "\\e[<31m", "<31", "m");
|
|
38
|
+
assert.deepEqual(result, { type: CODE_TYPES.PRIVATE, pos: 0, raw: "\\e[<31m", params: ["31"], command: "<m" });
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("parsePrivateCSI with > introducer", () => {
|
|
42
|
+
const result = parsePrivateCSI(0, "\\e[>c", ">", "c");
|
|
43
|
+
assert.deepEqual(result, { type: CODE_TYPES.PRIVATE, pos: 0, raw: "\\e[>c", params: [], command: ">c" });
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("parsePrivateCSI with = introducer", () => {
|
|
47
|
+
const result = parsePrivateCSI(0, "\\e[=1;2c", "=1;2", "c");
|
|
48
|
+
assert.deepEqual(result, {
|
|
49
|
+
type: CODE_TYPES.PRIVATE,
|
|
50
|
+
pos: 0,
|
|
51
|
+
raw: "\\e[=1;2c",
|
|
52
|
+
params: ["1", "2"],
|
|
53
|
+
command: "=c",
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { CODE_TYPES } from "../constants.ts";
|
|
2
|
+
import type { CONTROL_CODE } from "../types.ts";
|
|
3
|
+
|
|
4
|
+
export function parseCSI(pos: number, raw: string, data: string, final: string): CONTROL_CODE {
|
|
5
|
+
const params = [];
|
|
6
|
+
let intermediates = "";
|
|
7
|
+
if (data) {
|
|
8
|
+
let i = 0;
|
|
9
|
+
let paramSection = "";
|
|
10
|
+
while (i < data.length && data.charCodeAt(i) >= 0x30 && data.charCodeAt(i) <= 0x3f) {
|
|
11
|
+
paramSection += data[i];
|
|
12
|
+
i++;
|
|
13
|
+
}
|
|
14
|
+
intermediates = data.slice(i);
|
|
15
|
+
if (paramSection) {
|
|
16
|
+
let current = "";
|
|
17
|
+
for (let j = 0; j < paramSection.length; j++) {
|
|
18
|
+
if (paramSection[j] === ";") {
|
|
19
|
+
params.push(current || "-1");
|
|
20
|
+
current = "";
|
|
21
|
+
} else {
|
|
22
|
+
current += paramSection[j];
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
params.push(current || "-1");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const command = intermediates + final;
|
|
29
|
+
return { type: CODE_TYPES.CSI, pos, raw, command, params };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function parsePrivateCSI(pos: number, raw: string, data: string, final: string): CONTROL_CODE {
|
|
33
|
+
const privateIndicator = data[0];
|
|
34
|
+
const withoutIndicator = data.slice(1);
|
|
35
|
+
const match = withoutIndicator.match(/^([\d;]*)(.*)/);
|
|
36
|
+
const paramsRaw = match?.[1] ?? "";
|
|
37
|
+
const intermediates = match?.[2] ?? "";
|
|
38
|
+
const command = `${privateIndicator}${intermediates}${final}`;
|
|
39
|
+
const params = [];
|
|
40
|
+
if (paramsRaw) {
|
|
41
|
+
let current = "";
|
|
42
|
+
for (let i = 0; i < paramsRaw.length; i++) {
|
|
43
|
+
if (paramsRaw[i] === ";") {
|
|
44
|
+
params.push(current || "-1");
|
|
45
|
+
current = "";
|
|
46
|
+
} else {
|
|
47
|
+
current += paramsRaw[i];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
params.push(current || "-1");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return { type: CODE_TYPES.PRIVATE, pos, raw, command, params };
|
|
54
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { parseDCS } from "./dcs.ts";
|
|
4
|
+
import { CODE_TYPES } from "../constants.ts";
|
|
5
|
+
|
|
6
|
+
test("parseDCS simple sequence", () => {
|
|
7
|
+
const result = parseDCS(0, "\\eP0;1|name\\e\\\\", "0;1|name");
|
|
8
|
+
assert.deepEqual(result, {
|
|
9
|
+
type: CODE_TYPES.DCS,
|
|
10
|
+
pos: 0,
|
|
11
|
+
raw: "\\eP0;1|name\\e\\\\",
|
|
12
|
+
params: ["0;1|name"],
|
|
13
|
+
command: "",
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("parseDCS with missing parameters", () => {
|
|
18
|
+
const result = parseDCS(0, "\\eP$q;\\e\\\\", "$q;");
|
|
19
|
+
assert.deepEqual(result, {
|
|
20
|
+
type: CODE_TYPES.DCS,
|
|
21
|
+
pos: 0,
|
|
22
|
+
raw: "\\eP$q;\\e\\\\",
|
|
23
|
+
params: ["-1", "-1"],
|
|
24
|
+
command: "$q",
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("parseDCS with known pattern", () => {
|
|
29
|
+
const result = parseDCS(0, "\\eP$qm\\e\\\\", "$qm");
|
|
30
|
+
assert.deepEqual(result, { type: CODE_TYPES.DCS, pos: 0, raw: "\\eP$qm\\e\\\\", params: ["m"], command: "$q" });
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("parseDCS empty data", () => {
|
|
34
|
+
const result = parseDCS(0, "\\eP\\e\\\\", "");
|
|
35
|
+
assert.deepEqual(result, { type: CODE_TYPES.DCS, pos: 0, raw: "\\eP\\e\\\\", command: "", params: [] });
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("parseDCS unknown pattern", () => {
|
|
39
|
+
const result = parseDCS(0, "\\ePunknown\\e\\\\", "unknown");
|
|
40
|
+
assert.deepEqual(result, {
|
|
41
|
+
type: CODE_TYPES.DCS,
|
|
42
|
+
pos: 0,
|
|
43
|
+
raw: "\\ePunknown\\e\\\\",
|
|
44
|
+
command: "",
|
|
45
|
+
params: ["unknown"],
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { CODE_TYPES } from "../constants.ts";
|
|
2
|
+
import type { CONTROL_CODE } from "../types.ts";
|
|
3
|
+
|
|
4
|
+
const DCS_PATTERNS = new Map([
|
|
5
|
+
["$q", 2],
|
|
6
|
+
["+q", 2],
|
|
7
|
+
["+p", 2],
|
|
8
|
+
["|", 1],
|
|
9
|
+
["{", 1],
|
|
10
|
+
]);
|
|
11
|
+
|
|
12
|
+
export function parseDCS(pos: number, raw: string, data: string): CONTROL_CODE {
|
|
13
|
+
if (!data) return { type: CODE_TYPES.DCS, pos, raw, command: "", params: [] };
|
|
14
|
+
|
|
15
|
+
for (const [pattern, length] of DCS_PATTERNS) {
|
|
16
|
+
if (data.startsWith(pattern)) {
|
|
17
|
+
const remainder = data.slice(length);
|
|
18
|
+
const params = [];
|
|
19
|
+
if (remainder) {
|
|
20
|
+
let current = "";
|
|
21
|
+
for (let i = 0; i < remainder.length; i++) {
|
|
22
|
+
if (remainder[i] === ";") {
|
|
23
|
+
params.push(current || "-1");
|
|
24
|
+
current = "";
|
|
25
|
+
} else {
|
|
26
|
+
current += remainder[i];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
params.push(current || "-1");
|
|
30
|
+
}
|
|
31
|
+
return { type: CODE_TYPES.DCS, pos, raw, command: pattern, params };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return { type: CODE_TYPES.DCS, pos, raw, command: "", params: [data] };
|
|
36
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { parseDEC } from "./dec.ts";
|
|
4
|
+
import { CODE_TYPES } from "../constants.ts";
|
|
5
|
+
|
|
6
|
+
test("parseDEC basic sequence", () => {
|
|
7
|
+
const result = parseDEC(0, "\\e[?25h", "?25", "h");
|
|
8
|
+
assert.deepEqual(result, { type: CODE_TYPES.DEC, pos: 0, raw: "\\e[?25h", params: ["25"], command: "h" });
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test("parseDEC with missing parameters", () => {
|
|
12
|
+
const result = parseDEC(0, "\\e[?;h", "?;", "h");
|
|
13
|
+
assert.deepEqual(result, { type: CODE_TYPES.DEC, pos: 0, raw: "\\e[?;h", params: ["-1", "-1"], command: "h" });
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("parseDEC with intermediate bytes", () => {
|
|
17
|
+
const result = parseDEC(0, "\\e[?1$p", "?1$", "p");
|
|
18
|
+
assert.deepEqual(result, { type: CODE_TYPES.DEC, pos: 0, raw: "\\e[?1$p", params: ["1"], command: "$p" });
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("parseDEC multiple parameters with intermediates", () => {
|
|
22
|
+
const result = parseDEC(0, "\\e[?1;2$p", "?1;2$", "p");
|
|
23
|
+
assert.deepEqual(result, { type: CODE_TYPES.DEC, pos: 0, raw: "\\e[?1;2$p", params: ["1", "2"], command: "$p" });
|
|
24
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { CODE_TYPES } from "../constants.ts";
|
|
2
|
+
import type { CONTROL_CODE } from "../types.ts";
|
|
3
|
+
|
|
4
|
+
export function parseDEC(pos: number, raw: string, data: string, final: string): CONTROL_CODE {
|
|
5
|
+
const withoutPrefix = data.slice(1);
|
|
6
|
+
let i = 0;
|
|
7
|
+
let paramsRaw = "";
|
|
8
|
+
while (
|
|
9
|
+
i < withoutPrefix.length &&
|
|
10
|
+
((withoutPrefix.charCodeAt(i) >= 48 && withoutPrefix.charCodeAt(i) <= 57) || withoutPrefix[i] === ";")
|
|
11
|
+
) {
|
|
12
|
+
paramsRaw += withoutPrefix[i];
|
|
13
|
+
i++;
|
|
14
|
+
}
|
|
15
|
+
const command = withoutPrefix.slice(i) + final;
|
|
16
|
+
const params = [];
|
|
17
|
+
if (paramsRaw) {
|
|
18
|
+
let current = "";
|
|
19
|
+
for (let j = 0; j < paramsRaw.length; j++) {
|
|
20
|
+
if (paramsRaw[j] === ";") {
|
|
21
|
+
params.push(current || "-1");
|
|
22
|
+
current = "";
|
|
23
|
+
} else {
|
|
24
|
+
current += paramsRaw[j];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
params.push(current || "-1");
|
|
28
|
+
}
|
|
29
|
+
return { type: CODE_TYPES.DEC, pos, raw, command, params };
|
|
30
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { parseESC } from "./esc.ts";
|
|
4
|
+
import { CODE_TYPES } from "../constants.ts";
|
|
5
|
+
|
|
6
|
+
test("parseESC simple command", () => {
|
|
7
|
+
const result = parseESC(0, "\\e=", "=");
|
|
8
|
+
assert.deepEqual(result, { type: CODE_TYPES.ESC, pos: 0, raw: "\\e=", command: "=", params: [] });
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test("parseESC with data", () => {
|
|
12
|
+
const result = parseESC(0, "\\e(A", "(", "A");
|
|
13
|
+
assert.deepEqual(result, { type: CODE_TYPES.ESC, pos: 0, raw: "\\e(A", command: "(", params: ["A"] });
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("parseESC charset sequence", () => {
|
|
17
|
+
const result = parseESC(0, "\\e)A", ")", "A");
|
|
18
|
+
assert.deepEqual(result, { type: CODE_TYPES.ESC, pos: 0, raw: "\\e)A", command: ")", params: ["A"] });
|
|
19
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { CODE_TYPES } from "../constants.ts";
|
|
2
|
+
import type { CONTROL_CODE } from "../types.ts";
|
|
3
|
+
|
|
4
|
+
export function parseESC(pos: number, raw: string, command: string, data?: string): CONTROL_CODE {
|
|
5
|
+
return { type: CODE_TYPES.ESC, pos, raw, command, params: data ? [data] : [] };
|
|
6
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { parseOSC } from "./osc.ts";
|
|
4
|
+
import { CODE_TYPES } from "../constants.ts";
|
|
5
|
+
|
|
6
|
+
test("parseOSC simple command", () => {
|
|
7
|
+
const result = parseOSC(0, "\\e]8;;http://example.com\\a", "8;;http://example.com");
|
|
8
|
+
assert.deepEqual(result, {
|
|
9
|
+
type: CODE_TYPES.OSC,
|
|
10
|
+
pos: 0,
|
|
11
|
+
raw: "\\e]8;;http://example.com\\a",
|
|
12
|
+
params: ["", "http://example.com"],
|
|
13
|
+
command: "8",
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("parseOSC no parameters", () => {
|
|
18
|
+
const result = parseOSC(0, "\\e]0\\a", "0");
|
|
19
|
+
assert.deepEqual(result, { type: CODE_TYPES.OSC, pos: 0, raw: "\\e]0\\a", command: "0", params: [] });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("parseOSC with trailing semicolon (empty remainder)", () => {
|
|
23
|
+
const result = parseOSC(0, "\\e]8;\\a", "8;");
|
|
24
|
+
assert.deepEqual(result, { type: CODE_TYPES.OSC, pos: 0, raw: "\\e]8;\\a", command: "8", params: [] });
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("parseOSC real-world example", () => {
|
|
28
|
+
const result = parseOSC(0, "\\e]2;Window Title\\a", "2;Window Title");
|
|
29
|
+
assert.deepEqual(result, {
|
|
30
|
+
type: CODE_TYPES.OSC,
|
|
31
|
+
pos: 0,
|
|
32
|
+
raw: "\\e]2;Window Title\\a",
|
|
33
|
+
command: "2",
|
|
34
|
+
params: ["Window Title"],
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { CODE_TYPES } from "../constants.ts";
|
|
2
|
+
import type { CONTROL_CODE } from "../types.ts";
|
|
3
|
+
|
|
4
|
+
export function parseOSC(pos: number, raw: string, data: string): CONTROL_CODE {
|
|
5
|
+
const semicolonIndex = data.indexOf(";");
|
|
6
|
+
if (semicolonIndex === -1) {
|
|
7
|
+
return { type: CODE_TYPES.OSC, pos, raw, command: data, params: [] };
|
|
8
|
+
}
|
|
9
|
+
const command = data.slice(0, semicolonIndex);
|
|
10
|
+
const remainder = data.slice(semicolonIndex + 1);
|
|
11
|
+
|
|
12
|
+
if (command === "1337") return { type: CODE_TYPES.OSC, pos, raw, command, params: [remainder] };
|
|
13
|
+
|
|
14
|
+
const params = [];
|
|
15
|
+
if (remainder) {
|
|
16
|
+
let current = "";
|
|
17
|
+
for (let i = 0; i < remainder.length; i++) {
|
|
18
|
+
if (remainder[i] === ";") {
|
|
19
|
+
params.push(current);
|
|
20
|
+
current = "";
|
|
21
|
+
} else {
|
|
22
|
+
current += remainder[i];
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
params.push(current);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return { type: CODE_TYPES.OSC, pos, raw, command, params };
|
|
29
|
+
}
|