@teammates/consolonia 0.7.0 → 0.7.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/dist/__tests__/ansi.test.js +332 -5
- package/dist/__tests__/input.test.js +157 -0
- package/dist/ansi/esc.d.ts +48 -4
- package/dist/ansi/esc.js +86 -4
- package/dist/ansi/terminal-env.d.ts +34 -0
- package/dist/ansi/terminal-env.js +206 -0
- package/dist/ansi/win32-console.d.ts +28 -0
- package/dist/ansi/win32-console.js +122 -0
- package/dist/app.d.ts +4 -0
- package/dist/app.js +34 -28
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/input/mouse-matcher.d.ts +21 -3
- package/dist/input/mouse-matcher.js +123 -30
- package/package.json +4 -1
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Parses
|
|
2
|
+
* Parses terminal mouse tracking sequences.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Supported formats:
|
|
5
|
+
* SGR: \x1b[<Cb;Cx;CyM (press/motion)
|
|
6
|
+
* \x1b[<Cb;Cx;Cym (release)
|
|
7
|
+
* SGR-Pixels: \x1b[<Cb;Cx;CyM (same wire format as SGR, pixel coords)
|
|
8
|
+
* \x1b[<Cb;Cx;Cym
|
|
9
|
+
* X10: \x1b[M Cb Cx Cy (classic xterm byte encoding)
|
|
10
|
+
* UTF-8: \x1b[M Cb Cx Cy (same prefix as X10, UTF-8 encoded coords)
|
|
11
|
+
* URXVT: \x1b[Cb;Cx;CyM (decimal params, no < prefix)
|
|
6
12
|
*
|
|
7
13
|
* Cb encodes button and modifiers:
|
|
8
14
|
* bits 0-1: 0=left, 1=middle, 2=right
|
|
@@ -13,15 +19,27 @@
|
|
|
13
19
|
* bit 4 (+16): ctrl
|
|
14
20
|
*
|
|
15
21
|
* Cx, Cy are 1-based coordinates.
|
|
22
|
+
*
|
|
23
|
+
* Note: UTF-8 mode uses the same \x1b[M prefix as X10 but encodes
|
|
24
|
+
* coordinates as UTF-8 characters for values > 127. Node.js decodes
|
|
25
|
+
* UTF-8 stdin automatically, so the X10 parser handles both formats.
|
|
26
|
+
*
|
|
27
|
+
* Note: SGR-Pixels mode uses the same wire format as SGR but reports
|
|
28
|
+
* pixel coordinates instead of cell coordinates. These are passed
|
|
29
|
+
* through as-is (the caller must convert to cells if needed).
|
|
16
30
|
*/
|
|
17
31
|
import { type InputEvent } from "./events.js";
|
|
18
32
|
import { type IMatcher, MatchResult } from "./matcher.js";
|
|
19
33
|
export declare class MouseMatcher implements IMatcher {
|
|
20
34
|
private state;
|
|
21
35
|
private params;
|
|
36
|
+
private x10Bytes;
|
|
37
|
+
private urxvtParams;
|
|
22
38
|
private result;
|
|
23
39
|
append(char: string): MatchResult;
|
|
24
40
|
flush(): InputEvent | null;
|
|
25
41
|
reset(): void;
|
|
26
42
|
private finalize;
|
|
43
|
+
private finalizeUrxvt;
|
|
44
|
+
private finalizeX10;
|
|
27
45
|
}
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Parses
|
|
2
|
+
* Parses terminal mouse tracking sequences.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Supported formats:
|
|
5
|
+
* SGR: \x1b[<Cb;Cx;CyM (press/motion)
|
|
6
|
+
* \x1b[<Cb;Cx;Cym (release)
|
|
7
|
+
* SGR-Pixels: \x1b[<Cb;Cx;CyM (same wire format as SGR, pixel coords)
|
|
8
|
+
* \x1b[<Cb;Cx;Cym
|
|
9
|
+
* X10: \x1b[M Cb Cx Cy (classic xterm byte encoding)
|
|
10
|
+
* UTF-8: \x1b[M Cb Cx Cy (same prefix as X10, UTF-8 encoded coords)
|
|
11
|
+
* URXVT: \x1b[Cb;Cx;CyM (decimal params, no < prefix)
|
|
6
12
|
*
|
|
7
13
|
* Cb encodes button and modifiers:
|
|
8
14
|
* bits 0-1: 0=left, 1=middle, 2=right
|
|
@@ -13,6 +19,14 @@
|
|
|
13
19
|
* bit 4 (+16): ctrl
|
|
14
20
|
*
|
|
15
21
|
* Cx, Cy are 1-based coordinates.
|
|
22
|
+
*
|
|
23
|
+
* Note: UTF-8 mode uses the same \x1b[M prefix as X10 but encodes
|
|
24
|
+
* coordinates as UTF-8 characters for values > 127. Node.js decodes
|
|
25
|
+
* UTF-8 stdin automatically, so the X10 parser handles both formats.
|
|
26
|
+
*
|
|
27
|
+
* Note: SGR-Pixels mode uses the same wire format as SGR but reports
|
|
28
|
+
* pixel coordinates instead of cell coordinates. These are passed
|
|
29
|
+
* through as-is (the caller must convert to cells if needed).
|
|
16
30
|
*/
|
|
17
31
|
import { mouseEvent } from "./events.js";
|
|
18
32
|
import { MatchResult } from "./matcher.js";
|
|
@@ -24,12 +38,18 @@ var State;
|
|
|
24
38
|
State[State["GotEsc"] = 1] = "GotEsc";
|
|
25
39
|
/** Got \x1b[ */
|
|
26
40
|
State[State["GotBracket"] = 2] = "GotBracket";
|
|
27
|
-
/** Got \x1b[< — now reading params until M or m */
|
|
41
|
+
/** Got \x1b[< — now reading SGR/SGR-Pixels params until M or m */
|
|
28
42
|
State[State["Reading"] = 3] = "Reading";
|
|
43
|
+
/** Got \x1b[M — now reading three encoded bytes (X10 or UTF-8) */
|
|
44
|
+
State[State["ReadingX10"] = 4] = "ReadingX10";
|
|
45
|
+
/** Got \x1b[ followed by a digit — reading URXVT decimal params until M */
|
|
46
|
+
State[State["ReadingUrxvt"] = 5] = "ReadingUrxvt";
|
|
29
47
|
})(State || (State = {}));
|
|
30
48
|
export class MouseMatcher {
|
|
31
49
|
state = State.Idle;
|
|
32
50
|
params = "";
|
|
51
|
+
x10Bytes = [];
|
|
52
|
+
urxvtParams = "";
|
|
33
53
|
result = null;
|
|
34
54
|
append(char) {
|
|
35
55
|
switch (this.state) {
|
|
@@ -52,6 +72,17 @@ export class MouseMatcher {
|
|
|
52
72
|
this.params = "";
|
|
53
73
|
return MatchResult.Partial;
|
|
54
74
|
}
|
|
75
|
+
if (char === "M") {
|
|
76
|
+
this.state = State.ReadingX10;
|
|
77
|
+
this.x10Bytes = [];
|
|
78
|
+
return MatchResult.Partial;
|
|
79
|
+
}
|
|
80
|
+
// URXVT: \x1b[ followed by a digit starts decimal param reading
|
|
81
|
+
if (char >= "0" && char <= "9") {
|
|
82
|
+
this.state = State.ReadingUrxvt;
|
|
83
|
+
this.urxvtParams = char;
|
|
84
|
+
return MatchResult.Partial;
|
|
85
|
+
}
|
|
55
86
|
this.state = State.Idle;
|
|
56
87
|
return MatchResult.NoMatch;
|
|
57
88
|
case State.Reading: {
|
|
@@ -69,6 +100,26 @@ export class MouseMatcher {
|
|
|
69
100
|
this.params = "";
|
|
70
101
|
return MatchResult.NoMatch;
|
|
71
102
|
}
|
|
103
|
+
case State.ReadingX10:
|
|
104
|
+
this.x10Bytes.push(char);
|
|
105
|
+
if (this.x10Bytes.length < 3) {
|
|
106
|
+
return MatchResult.Partial;
|
|
107
|
+
}
|
|
108
|
+
return this.finalizeX10();
|
|
109
|
+
case State.ReadingUrxvt: {
|
|
110
|
+
if (char === "M") {
|
|
111
|
+
return this.finalizeUrxvt();
|
|
112
|
+
}
|
|
113
|
+
const c = char.charCodeAt(0);
|
|
114
|
+
if ((c >= 0x30 && c <= 0x39) || char === ";") {
|
|
115
|
+
this.urxvtParams += char;
|
|
116
|
+
return MatchResult.Partial;
|
|
117
|
+
}
|
|
118
|
+
// Not a valid URXVT sequence — bail out
|
|
119
|
+
this.state = State.Idle;
|
|
120
|
+
this.urxvtParams = "";
|
|
121
|
+
return MatchResult.NoMatch;
|
|
122
|
+
}
|
|
72
123
|
default:
|
|
73
124
|
return MatchResult.NoMatch;
|
|
74
125
|
}
|
|
@@ -81,6 +132,8 @@ export class MouseMatcher {
|
|
|
81
132
|
reset() {
|
|
82
133
|
this.state = State.Idle;
|
|
83
134
|
this.params = "";
|
|
135
|
+
this.x10Bytes = [];
|
|
136
|
+
this.urxvtParams = "";
|
|
84
137
|
this.result = null;
|
|
85
138
|
}
|
|
86
139
|
finalize(isRelease) {
|
|
@@ -96,37 +149,77 @@ export class MouseMatcher {
|
|
|
96
149
|
if (Number.isNaN(cb) || Number.isNaN(cx) || Number.isNaN(cy)) {
|
|
97
150
|
return MatchResult.NoMatch;
|
|
98
151
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const buttonBits = cb & 3;
|
|
106
|
-
const highBits = cb & (64 | 128);
|
|
107
|
-
let button;
|
|
108
|
-
let type;
|
|
109
|
-
if (highBits === 64) {
|
|
110
|
-
// Wheel events
|
|
111
|
-
button = "none";
|
|
112
|
-
type = buttonBits === 0 ? "wheelup" : "wheeldown";
|
|
152
|
+
if (isRelease) {
|
|
153
|
+
const shift = (cb & 4) !== 0;
|
|
154
|
+
const alt = (cb & 8) !== 0;
|
|
155
|
+
const ctrl = (cb & 16) !== 0;
|
|
156
|
+
this.result = mouseEvent(cx - 1, cy - 1, decodeButton(cb & 3), "release", shift, ctrl, alt);
|
|
157
|
+
return MatchResult.Complete;
|
|
113
158
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
159
|
+
this.result = decodeMouseEvent(cb, cx, cy, true);
|
|
160
|
+
return this.result ? MatchResult.Complete : MatchResult.NoMatch;
|
|
161
|
+
}
|
|
162
|
+
finalizeUrxvt() {
|
|
163
|
+
this.state = State.Idle;
|
|
164
|
+
const parts = this.urxvtParams.split(";");
|
|
165
|
+
this.urxvtParams = "";
|
|
166
|
+
if (parts.length !== 3) {
|
|
167
|
+
return MatchResult.NoMatch;
|
|
168
|
+
}
|
|
169
|
+
const cb = parseInt(parts[0], 10);
|
|
170
|
+
const cx = parseInt(parts[1], 10);
|
|
171
|
+
const cy = parseInt(parts[2], 10);
|
|
172
|
+
if (Number.isNaN(cb) || Number.isNaN(cx) || Number.isNaN(cy)) {
|
|
173
|
+
return MatchResult.NoMatch;
|
|
117
174
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
175
|
+
// URXVT uses the same button encoding as X10 (button 3 = release)
|
|
176
|
+
this.result = decodeMouseEvent(cb, cx, cy, true);
|
|
177
|
+
return this.result ? MatchResult.Complete : MatchResult.NoMatch;
|
|
178
|
+
}
|
|
179
|
+
finalizeX10() {
|
|
180
|
+
this.state = State.Idle;
|
|
181
|
+
if (this.x10Bytes.length !== 3) {
|
|
182
|
+
this.x10Bytes = [];
|
|
183
|
+
return MatchResult.NoMatch;
|
|
121
184
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
185
|
+
const [cbChar, cxChar, cyChar] = this.x10Bytes;
|
|
186
|
+
this.x10Bytes = [];
|
|
187
|
+
const cb = cbChar.charCodeAt(0) - 32;
|
|
188
|
+
const cx = cxChar.charCodeAt(0) - 32;
|
|
189
|
+
const cy = cyChar.charCodeAt(0) - 32;
|
|
190
|
+
if (cb < 0 || cx <= 0 || cy <= 0) {
|
|
191
|
+
return MatchResult.NoMatch;
|
|
125
192
|
}
|
|
126
|
-
|
|
127
|
-
this.result
|
|
128
|
-
|
|
193
|
+
this.result = decodeMouseEvent(cb, cx, cy, true);
|
|
194
|
+
return this.result ? MatchResult.Complete : MatchResult.NoMatch;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function decodeMouseEvent(cb, cx, cy, x10ReleaseUsesButton3) {
|
|
198
|
+
const shift = (cb & 4) !== 0;
|
|
199
|
+
const alt = (cb & 8) !== 0;
|
|
200
|
+
const ctrl = (cb & 16) !== 0;
|
|
201
|
+
const isMotion = (cb & 32) !== 0;
|
|
202
|
+
const buttonBits = cb & 3;
|
|
203
|
+
const highBits = cb & (64 | 128);
|
|
204
|
+
let button;
|
|
205
|
+
let type;
|
|
206
|
+
if (highBits === 64) {
|
|
207
|
+
button = "none";
|
|
208
|
+
type = buttonBits === 0 ? "wheelup" : "wheeldown";
|
|
209
|
+
}
|
|
210
|
+
else if (x10ReleaseUsesButton3 && !isMotion && buttonBits === 3) {
|
|
211
|
+
button = "none";
|
|
212
|
+
type = "release";
|
|
213
|
+
}
|
|
214
|
+
else if (isMotion) {
|
|
215
|
+
button = buttonBits === 3 ? "none" : decodeButton(buttonBits);
|
|
216
|
+
type = "move";
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
button = decodeButton(buttonBits);
|
|
220
|
+
type = "press";
|
|
129
221
|
}
|
|
222
|
+
return mouseEvent(cx - 1, cy - 1, button, type, shift, ctrl, alt);
|
|
130
223
|
}
|
|
131
224
|
function decodeButton(bits) {
|
|
132
225
|
switch (bits) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@teammates/consolonia",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2",
|
|
4
4
|
"description": "Terminal UI rendering engine inspired by Consolonia. Pixel-level compositing with ANSI output.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -41,5 +41,8 @@
|
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"marked": "^17.0.4"
|
|
44
|
+
},
|
|
45
|
+
"optionalDependencies": {
|
|
46
|
+
"koffi": "^2.9.0"
|
|
44
47
|
}
|
|
45
48
|
}
|