@bomb.sh/tty 0.0.0-register.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 +227 -0
- package/esm/input-native.d.ts +102 -0
- package/esm/input-native.js +150 -0
- package/esm/input.d.ts +226 -0
- package/esm/input.js +228 -0
- package/esm/mod.d.ts +5 -0
- package/esm/mod.js +5 -0
- package/esm/ops.d.ts +106 -0
- package/esm/ops.js +307 -0
- package/esm/package.json +3 -0
- package/esm/settings.d.ts +18 -0
- package/esm/settings.js +61 -0
- package/esm/term-native.d.ts +21 -0
- package/esm/term-native.js +97 -0
- package/esm/term.d.ts +55 -0
- package/esm/term.js +81 -0
- package/esm/termcodes.d.ts +63 -0
- package/esm/termcodes.js +85 -0
- package/esm/typedef.d.ts +44 -0
- package/esm/typedef.js +84 -0
- package/esm/wasm.d.ts +1 -0
- package/esm/wasm.js +18 -0
- package/package.json +26 -0
package/esm/input.js
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal input parser.
|
|
3
|
+
*
|
|
4
|
+
* Thin wrapper around WASM `input_*` functions that decode raw VT/ANSI
|
|
5
|
+
* escape sequences into structured events. All parsing logic, state
|
|
6
|
+
* management, and buffering lives in WASM.
|
|
7
|
+
*/
|
|
8
|
+
import { createInputNative, EVENT_CURSOR, EVENT_KEY, EVENT_MOUSE, EVENT_RESIZE, KEY_ALT_LEFT, KEY_ALT_RIGHT, KEY_ARROW_DOWN, KEY_ARROW_LEFT, KEY_ARROW_RIGHT, KEY_ARROW_UP, KEY_BACKSPACE, KEY_BACKTAB, KEY_CAPS_LOCK, KEY_CONTROL_LEFT, KEY_CONTROL_RIGHT, KEY_DELETE, KEY_END, KEY_ENTER, KEY_ESC, KEY_F1, KEY_F10, KEY_F11, KEY_F12, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_HOME, KEY_HYPER_LEFT, KEY_HYPER_RIGHT, KEY_INSERT, KEY_META_LEFT, KEY_META_RIGHT, KEY_MOUSE_LEFT, KEY_MOUSE_MIDDLE, KEY_MOUSE_RELEASE, KEY_MOUSE_RIGHT, KEY_MOUSE_WHEEL_DOWN, KEY_MOUSE_WHEEL_UP, KEY_NUM_LOCK, KEY_NUMPAD_0, KEY_NUMPAD_1, KEY_NUMPAD_2, KEY_NUMPAD_3, KEY_NUMPAD_4, KEY_NUMPAD_5, KEY_NUMPAD_6, KEY_NUMPAD_7, KEY_NUMPAD_8, KEY_NUMPAD_9, KEY_NUMPAD_ADD, KEY_NUMPAD_DECIMAL, KEY_NUMPAD_DIVIDE, KEY_NUMPAD_ENTER, KEY_NUMPAD_EQUAL, KEY_NUMPAD_MULTIPLY, KEY_NUMPAD_SUBTRACT, KEY_PGDN, KEY_PGUP, KEY_SCROLL_LOCK, KEY_SHIFT_LEFT, KEY_SHIFT_RIGHT, KEY_SUPER_LEFT, KEY_SUPER_RIGHT, KEY_TAB, MAX_TERMINFO, MOD_ALT, MOD_CTRL, MOD_MOTION, MOD_RELEASE, MOD_SHIFT, readEvent, SCAN_BUFFER_SIZE, } from "./input-native.js";
|
|
9
|
+
export async function createInput(options = {}) {
|
|
10
|
+
let { escLatency = 25, terminfo } = options;
|
|
11
|
+
if (terminfo && terminfo.byteLength > MAX_TERMINFO) {
|
|
12
|
+
throw new RangeError(`terminfo exceeds ${MAX_TERMINFO} byte limit (got ${terminfo.byteLength})`);
|
|
13
|
+
}
|
|
14
|
+
let native = await createInputNative(escLatency);
|
|
15
|
+
return {
|
|
16
|
+
scan(bytes = new Uint8Array(0)) {
|
|
17
|
+
let now = Date.now();
|
|
18
|
+
let events = [];
|
|
19
|
+
let offset = 0;
|
|
20
|
+
while (offset < bytes.length || (offset === 0 && bytes.length === 0)) {
|
|
21
|
+
let chunk = bytes.subarray(offset, offset + SCAN_BUFFER_SIZE);
|
|
22
|
+
if (chunk.length > 0) {
|
|
23
|
+
new Uint8Array(native.memory.buffer).set(chunk, native.buffer);
|
|
24
|
+
}
|
|
25
|
+
let accepted = native.scan(native.state, native.buffer, chunk.length, now);
|
|
26
|
+
let count = native.count(native.state);
|
|
27
|
+
let view = new DataView(native.memory.buffer);
|
|
28
|
+
for (let i = 0; i < count; i++) {
|
|
29
|
+
let ptr = native.event(native.state, i);
|
|
30
|
+
if (ptr !== 0) {
|
|
31
|
+
events.push(mapEvent(readEvent(view, ptr)));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
offset += accepted;
|
|
35
|
+
if (accepted < chunk.length) {
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
if (bytes.length === 0) {
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
let delay = native.delay(native.state);
|
|
43
|
+
if (delay > 0) {
|
|
44
|
+
return { events, pending: { delay } };
|
|
45
|
+
}
|
|
46
|
+
return { events };
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const KEY_NAMES = new Map([
|
|
51
|
+
[KEY_F1, "F1"],
|
|
52
|
+
[KEY_F2, "F2"],
|
|
53
|
+
[KEY_F3, "F3"],
|
|
54
|
+
[KEY_F4, "F4"],
|
|
55
|
+
[KEY_F5, "F5"],
|
|
56
|
+
[KEY_F6, "F6"],
|
|
57
|
+
[KEY_F7, "F7"],
|
|
58
|
+
[KEY_F8, "F8"],
|
|
59
|
+
[KEY_F9, "F9"],
|
|
60
|
+
[KEY_F10, "F10"],
|
|
61
|
+
[KEY_F11, "F11"],
|
|
62
|
+
[KEY_F12, "F12"],
|
|
63
|
+
[KEY_ARROW_UP, "ArrowUp"],
|
|
64
|
+
[KEY_ARROW_DOWN, "ArrowDown"],
|
|
65
|
+
[KEY_ARROW_LEFT, "ArrowLeft"],
|
|
66
|
+
[KEY_ARROW_RIGHT, "ArrowRight"],
|
|
67
|
+
[KEY_HOME, "Home"],
|
|
68
|
+
[KEY_END, "End"],
|
|
69
|
+
[KEY_INSERT, "Insert"],
|
|
70
|
+
[KEY_DELETE, "Delete"],
|
|
71
|
+
[KEY_PGUP, "PageUp"],
|
|
72
|
+
[KEY_PGDN, "PageDown"],
|
|
73
|
+
[KEY_BACKTAB, "Backtab"],
|
|
74
|
+
[KEY_BACKSPACE, "Backspace"],
|
|
75
|
+
[KEY_TAB, "Tab"],
|
|
76
|
+
[KEY_ENTER, "Enter"],
|
|
77
|
+
[KEY_ESC, "Escape"],
|
|
78
|
+
[KEY_NUMPAD_0, "Numpad0"],
|
|
79
|
+
[KEY_NUMPAD_1, "Numpad1"],
|
|
80
|
+
[KEY_NUMPAD_2, "Numpad2"],
|
|
81
|
+
[KEY_NUMPAD_3, "Numpad3"],
|
|
82
|
+
[KEY_NUMPAD_4, "Numpad4"],
|
|
83
|
+
[KEY_NUMPAD_5, "Numpad5"],
|
|
84
|
+
[KEY_NUMPAD_6, "Numpad6"],
|
|
85
|
+
[KEY_NUMPAD_7, "Numpad7"],
|
|
86
|
+
[KEY_NUMPAD_8, "Numpad8"],
|
|
87
|
+
[KEY_NUMPAD_9, "Numpad9"],
|
|
88
|
+
[KEY_NUMPAD_DECIMAL, "NumpadDecimal"],
|
|
89
|
+
[KEY_NUMPAD_DIVIDE, "NumpadDivide"],
|
|
90
|
+
[KEY_NUMPAD_MULTIPLY, "NumpadMultiply"],
|
|
91
|
+
[KEY_NUMPAD_SUBTRACT, "NumpadSubtract"],
|
|
92
|
+
[KEY_NUMPAD_ADD, "NumpadAdd"],
|
|
93
|
+
[KEY_NUMPAD_ENTER, "NumpadEnter"],
|
|
94
|
+
[KEY_NUMPAD_EQUAL, "NumpadEqual"],
|
|
95
|
+
[KEY_SHIFT_LEFT, "ShiftLeft"],
|
|
96
|
+
[KEY_SHIFT_RIGHT, "ShiftRight"],
|
|
97
|
+
[KEY_CONTROL_LEFT, "ControlLeft"],
|
|
98
|
+
[KEY_CONTROL_RIGHT, "ControlRight"],
|
|
99
|
+
[KEY_ALT_LEFT, "AltLeft"],
|
|
100
|
+
[KEY_ALT_RIGHT, "AltRight"],
|
|
101
|
+
[KEY_SUPER_LEFT, "SuperLeft"],
|
|
102
|
+
[KEY_SUPER_RIGHT, "SuperRight"],
|
|
103
|
+
[KEY_HYPER_LEFT, "HyperLeft"],
|
|
104
|
+
[KEY_HYPER_RIGHT, "HyperRight"],
|
|
105
|
+
[KEY_META_LEFT, "MetaLeft"],
|
|
106
|
+
[KEY_META_RIGHT, "MetaRight"],
|
|
107
|
+
[KEY_CAPS_LOCK, "CapsLock"],
|
|
108
|
+
[KEY_NUM_LOCK, "NumLock"],
|
|
109
|
+
[KEY_SCROLL_LOCK, "ScrollLock"],
|
|
110
|
+
]);
|
|
111
|
+
const BUTTON_NAMES = new Map([
|
|
112
|
+
[KEY_MOUSE_LEFT, "left"],
|
|
113
|
+
[KEY_MOUSE_RIGHT, "right"],
|
|
114
|
+
[KEY_MOUSE_MIDDLE, "middle"],
|
|
115
|
+
[KEY_MOUSE_RELEASE, "release"],
|
|
116
|
+
]);
|
|
117
|
+
function mods(native) {
|
|
118
|
+
let m = {};
|
|
119
|
+
if (native.mod & MOD_ALT)
|
|
120
|
+
m.alt = true;
|
|
121
|
+
if (native.mod & MOD_CTRL)
|
|
122
|
+
m.ctrl = true;
|
|
123
|
+
if (native.mod & MOD_SHIFT)
|
|
124
|
+
m.shift = true;
|
|
125
|
+
return m;
|
|
126
|
+
}
|
|
127
|
+
function keyName(native) {
|
|
128
|
+
let name = KEY_NAMES.get(native.key);
|
|
129
|
+
if (name) {
|
|
130
|
+
return name;
|
|
131
|
+
}
|
|
132
|
+
else if (native.key === 0 && native.ch > 0) {
|
|
133
|
+
return String.fromCodePoint(native.ch);
|
|
134
|
+
}
|
|
135
|
+
else if (native.key > 0 && native.key < 0x20) {
|
|
136
|
+
return String.fromCharCode(native.key + 0x60);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
return String.fromCodePoint(native.ch || native.key);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function textFromNative(native) {
|
|
143
|
+
if (native.text.length === 0) {
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
return String.fromCodePoint(...native.text);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function mapKeyEvent(native) {
|
|
151
|
+
let key = keyName(native);
|
|
152
|
+
let code = (native.base > 0 ? String.fromCodePoint(native.base) : key);
|
|
153
|
+
let m = mods(native);
|
|
154
|
+
let isChar = !KEY_NAMES.has(native.key);
|
|
155
|
+
let text = textFromNative(native);
|
|
156
|
+
if (native.action === 3) {
|
|
157
|
+
return { type: "keyup", key, code, ...m };
|
|
158
|
+
}
|
|
159
|
+
let type = native.action === 2
|
|
160
|
+
? "keyrepeat"
|
|
161
|
+
: "keydown";
|
|
162
|
+
let ev = { type, key, code, ...m };
|
|
163
|
+
if (native.shifted > 0)
|
|
164
|
+
ev.shifted = String.fromCodePoint(native.shifted);
|
|
165
|
+
if (text) {
|
|
166
|
+
ev.text = text;
|
|
167
|
+
}
|
|
168
|
+
else if (isChar && type === "keydown" && native.ch > 0) {
|
|
169
|
+
ev.text = String.fromCodePoint(native.ch);
|
|
170
|
+
}
|
|
171
|
+
return ev;
|
|
172
|
+
}
|
|
173
|
+
function mapEvent(native) {
|
|
174
|
+
switch (native.type) {
|
|
175
|
+
case EVENT_KEY: {
|
|
176
|
+
return mapKeyEvent(native);
|
|
177
|
+
}
|
|
178
|
+
case EVENT_MOUSE: {
|
|
179
|
+
if (native.key === KEY_MOUSE_WHEEL_UP || native.key === KEY_MOUSE_WHEEL_DOWN) {
|
|
180
|
+
return {
|
|
181
|
+
type: "wheel",
|
|
182
|
+
direction: native.key === KEY_MOUSE_WHEEL_UP ? "up" : "down",
|
|
183
|
+
x: native.x,
|
|
184
|
+
y: native.y,
|
|
185
|
+
...mods(native),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
if (native.mod & MOD_MOTION) {
|
|
189
|
+
let button = BUTTON_NAMES.get(native.key) ?? "left";
|
|
190
|
+
return {
|
|
191
|
+
type: "mousemove",
|
|
192
|
+
button: button === "release" ? "left" : button,
|
|
193
|
+
x: native.x,
|
|
194
|
+
y: native.y,
|
|
195
|
+
...mods(native),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
let button = BUTTON_NAMES.get(native.key) ?? "left";
|
|
199
|
+
if (native.mod & MOD_RELEASE) {
|
|
200
|
+
return {
|
|
201
|
+
type: "mouseup",
|
|
202
|
+
button,
|
|
203
|
+
x: native.x,
|
|
204
|
+
y: native.y,
|
|
205
|
+
...mods(native),
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
return {
|
|
210
|
+
type: "mousedown",
|
|
211
|
+
button: button,
|
|
212
|
+
x: native.x,
|
|
213
|
+
y: native.y,
|
|
214
|
+
...mods(native),
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
case EVENT_RESIZE: {
|
|
219
|
+
return { type: "resize", width: native.w, height: native.h };
|
|
220
|
+
}
|
|
221
|
+
case EVENT_CURSOR: {
|
|
222
|
+
return { type: "cursor", row: native.y, column: native.x };
|
|
223
|
+
}
|
|
224
|
+
default: {
|
|
225
|
+
return mapKeyEvent(native);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
package/esm/mod.d.ts
ADDED
package/esm/mod.js
ADDED
package/esm/ops.d.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
declare const OP_OPEN_ELEMENT = 2;
|
|
2
|
+
declare const OP_TEXT = 3;
|
|
3
|
+
declare const OP_CLOSE_ELEMENT = 4;
|
|
4
|
+
declare const OP_SNAPSHOT = 5;
|
|
5
|
+
export declare function pack(ops: Op[], mem: ArrayBufferLike, offset: number, limit?: number): number;
|
|
6
|
+
export declare function rgba(r: number, g: number, b: number, a?: number): number;
|
|
7
|
+
export type SizingAxis = {
|
|
8
|
+
type: "fit";
|
|
9
|
+
min?: number;
|
|
10
|
+
max?: number;
|
|
11
|
+
} | {
|
|
12
|
+
type: "grow";
|
|
13
|
+
min?: number;
|
|
14
|
+
max?: number;
|
|
15
|
+
} | {
|
|
16
|
+
type: "percent";
|
|
17
|
+
value: number;
|
|
18
|
+
} | {
|
|
19
|
+
type: "fixed";
|
|
20
|
+
value: number;
|
|
21
|
+
};
|
|
22
|
+
export declare const fit: (min?: number, max?: number) => SizingAxis;
|
|
23
|
+
export declare const grow: (min?: number, max?: number) => SizingAxis;
|
|
24
|
+
export declare const percent: (value: number) => SizingAxis;
|
|
25
|
+
export declare const fixed: (value: number) => SizingAxis;
|
|
26
|
+
export interface CloseElement {
|
|
27
|
+
directive: typeof OP_CLOSE_ELEMENT;
|
|
28
|
+
}
|
|
29
|
+
export interface OpenElement {
|
|
30
|
+
directive: typeof OP_OPEN_ELEMENT;
|
|
31
|
+
id: string;
|
|
32
|
+
layout?: {
|
|
33
|
+
width?: SizingAxis;
|
|
34
|
+
height?: SizingAxis;
|
|
35
|
+
padding?: {
|
|
36
|
+
left?: number;
|
|
37
|
+
right?: number;
|
|
38
|
+
top?: number;
|
|
39
|
+
bottom?: number;
|
|
40
|
+
};
|
|
41
|
+
gap?: number;
|
|
42
|
+
direction?: "ltr" | "ttb";
|
|
43
|
+
alignX?: "left" | "center" | "right";
|
|
44
|
+
alignY?: "top" | "center" | "bottom";
|
|
45
|
+
};
|
|
46
|
+
bg?: number;
|
|
47
|
+
cornerRadius?: {
|
|
48
|
+
tl?: number;
|
|
49
|
+
tr?: number;
|
|
50
|
+
bl?: number;
|
|
51
|
+
br?: number;
|
|
52
|
+
};
|
|
53
|
+
border?: {
|
|
54
|
+
color: number;
|
|
55
|
+
bg?: number;
|
|
56
|
+
left?: number;
|
|
57
|
+
right?: number;
|
|
58
|
+
top?: number;
|
|
59
|
+
bottom?: number;
|
|
60
|
+
};
|
|
61
|
+
clip?: {
|
|
62
|
+
horizontal?: boolean;
|
|
63
|
+
vertical?: boolean;
|
|
64
|
+
};
|
|
65
|
+
floating?: {
|
|
66
|
+
x?: number;
|
|
67
|
+
y?: number;
|
|
68
|
+
expand?: {
|
|
69
|
+
width?: number;
|
|
70
|
+
height?: number;
|
|
71
|
+
};
|
|
72
|
+
parent?: number;
|
|
73
|
+
attachTo?: AttachTo;
|
|
74
|
+
attachPoints?: {
|
|
75
|
+
element?: AttachPoint;
|
|
76
|
+
parent?: AttachPoint;
|
|
77
|
+
};
|
|
78
|
+
pointerCaptureMode?: PointerCaptureMode;
|
|
79
|
+
clipTo?: ClipTo;
|
|
80
|
+
zIndex?: number;
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
export type AttachPoint = "left-top" | "left-center" | "left-bottom" | "center-top" | "center-center" | "center-bottom" | "right-top" | "right-center" | "right-bottom";
|
|
84
|
+
export type AttachTo = "none" | "parent" | "element" | "root";
|
|
85
|
+
export type PointerCaptureMode = "capture" | "passthrough";
|
|
86
|
+
export type ClipTo = "none" | "attached-parent";
|
|
87
|
+
export interface Text {
|
|
88
|
+
directive: typeof OP_TEXT;
|
|
89
|
+
content: string;
|
|
90
|
+
color?: number;
|
|
91
|
+
bg?: number;
|
|
92
|
+
fontSize?: number;
|
|
93
|
+
fontId?: number;
|
|
94
|
+
wrap?: number;
|
|
95
|
+
attrs?: number;
|
|
96
|
+
}
|
|
97
|
+
interface Snapshot {
|
|
98
|
+
directive: typeof OP_SNAPSHOT;
|
|
99
|
+
data: Uint8Array;
|
|
100
|
+
}
|
|
101
|
+
export type Op = OpenElement | Text | CloseElement | Snapshot;
|
|
102
|
+
export declare function open(id: string, props?: Omit<OpenElement, "directive" | "id">): OpenElement;
|
|
103
|
+
export declare function text(content: string, props?: Omit<Text, "directive" | "content">): Text;
|
|
104
|
+
export declare function close(): CloseElement;
|
|
105
|
+
export declare function snapshot(ops: Op[]): Op;
|
|
106
|
+
export {};
|
package/esm/ops.js
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/* Command buffer opcodes — mirrors ops.h */
|
|
2
|
+
const OP_OPEN_ELEMENT = 0x02;
|
|
3
|
+
const OP_TEXT = 0x03;
|
|
4
|
+
const OP_CLOSE_ELEMENT = 0x04;
|
|
5
|
+
const OP_SNAPSHOT = 0x05;
|
|
6
|
+
/* Property group masks for OPEN_ELEMENT */
|
|
7
|
+
const PROP_LAYOUT = 0x01;
|
|
8
|
+
const PROP_BG_COLOR = 0x02;
|
|
9
|
+
const PROP_CORNER_RADIUS = 0x04;
|
|
10
|
+
const PROP_BORDER = 0x08;
|
|
11
|
+
const PROP_CLIP = 0x10;
|
|
12
|
+
const PROP_FLOATING = 0x20;
|
|
13
|
+
const encoder = new TextEncoder();
|
|
14
|
+
function packAxis(view, offset, axis) {
|
|
15
|
+
let o = offset;
|
|
16
|
+
switch (axis.type) {
|
|
17
|
+
case "fit":
|
|
18
|
+
view.setUint32(o, 0, true);
|
|
19
|
+
o += 4;
|
|
20
|
+
view.setFloat32(o, axis.min ?? 0, true);
|
|
21
|
+
o += 4;
|
|
22
|
+
view.setFloat32(o, axis.max ?? 0, true);
|
|
23
|
+
o += 4;
|
|
24
|
+
break;
|
|
25
|
+
case "grow":
|
|
26
|
+
view.setUint32(o, 1, true);
|
|
27
|
+
o += 4;
|
|
28
|
+
view.setFloat32(o, axis.min ?? 0, true);
|
|
29
|
+
o += 4;
|
|
30
|
+
view.setFloat32(o, axis.max ?? 0, true);
|
|
31
|
+
o += 4;
|
|
32
|
+
break;
|
|
33
|
+
case "percent":
|
|
34
|
+
view.setUint32(o, 2, true);
|
|
35
|
+
o += 4;
|
|
36
|
+
view.setFloat32(o, axis.value, true);
|
|
37
|
+
o += 4;
|
|
38
|
+
view.setFloat32(o, 0, true);
|
|
39
|
+
o += 4;
|
|
40
|
+
break;
|
|
41
|
+
case "fixed":
|
|
42
|
+
view.setUint32(o, 3, true);
|
|
43
|
+
o += 4;
|
|
44
|
+
view.setFloat32(o, axis.value, true);
|
|
45
|
+
o += 4;
|
|
46
|
+
view.setFloat32(o, 0, true);
|
|
47
|
+
o += 4;
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
return o;
|
|
51
|
+
}
|
|
52
|
+
function packString(view, bytes, o, end, context) {
|
|
53
|
+
let paddedLength = Math.ceil(bytes.length / 4) * 4;
|
|
54
|
+
let next = o + 4 + paddedLength;
|
|
55
|
+
if (next > end) {
|
|
56
|
+
throw new RangeError(`transfer buffer capacity exceeded while packing ${context} ` +
|
|
57
|
+
`(${next} byte offset, ${end} byte limit). ` +
|
|
58
|
+
`Render a smaller visible slice or reduce frame content.`);
|
|
59
|
+
}
|
|
60
|
+
view.setUint32(o, bytes.length, true);
|
|
61
|
+
o += 4;
|
|
62
|
+
new Uint8Array(view.buffer).set(bytes, o);
|
|
63
|
+
o += paddedLength;
|
|
64
|
+
return o;
|
|
65
|
+
}
|
|
66
|
+
export function pack(ops, mem, offset, limit) {
|
|
67
|
+
let view = new DataView(mem);
|
|
68
|
+
let end = limit ?? mem.byteLength;
|
|
69
|
+
let o = offset;
|
|
70
|
+
for (let op of ops) {
|
|
71
|
+
switch (op.directive) {
|
|
72
|
+
case OP_CLOSE_ELEMENT:
|
|
73
|
+
view.setUint32(o, op.directive, true);
|
|
74
|
+
o += 4;
|
|
75
|
+
break;
|
|
76
|
+
case OP_OPEN_ELEMENT: {
|
|
77
|
+
view.setUint32(o, OP_OPEN_ELEMENT, true);
|
|
78
|
+
o += 4;
|
|
79
|
+
let bytes = encoder.encode(op.id);
|
|
80
|
+
o = packString(view, bytes, o, end, "element id");
|
|
81
|
+
let mask = 0;
|
|
82
|
+
if (op.layout)
|
|
83
|
+
mask |= PROP_LAYOUT;
|
|
84
|
+
if (op.bg !== undefined)
|
|
85
|
+
mask |= PROP_BG_COLOR;
|
|
86
|
+
if (op.cornerRadius)
|
|
87
|
+
mask |= PROP_CORNER_RADIUS;
|
|
88
|
+
if (op.border)
|
|
89
|
+
mask |= PROP_BORDER;
|
|
90
|
+
if (op.clip)
|
|
91
|
+
mask |= PROP_CLIP;
|
|
92
|
+
if (op.floating)
|
|
93
|
+
mask |= PROP_FLOATING;
|
|
94
|
+
view.setUint32(o, mask, true);
|
|
95
|
+
o += 4;
|
|
96
|
+
if (op.layout) {
|
|
97
|
+
let l = op.layout;
|
|
98
|
+
o = packAxis(view, o, l.width ?? { type: "fit" });
|
|
99
|
+
o = packAxis(view, o, l.height ?? { type: "fit" });
|
|
100
|
+
let p = l.padding ?? {};
|
|
101
|
+
view.setUint32(o, (p.left ?? 0) | ((p.right ?? 0) << 8) | ((p.top ?? 0) << 16) |
|
|
102
|
+
((p.bottom ?? 0) << 24), true);
|
|
103
|
+
o += 4;
|
|
104
|
+
view.setUint32(o, (l.gap ?? 0) | ((l.direction === "ttb" ? 1 : 0) << 16), true);
|
|
105
|
+
o += 4;
|
|
106
|
+
let alignX = l.alignX === "right" ? 1 : l.alignX === "center" ? 2 : 0;
|
|
107
|
+
let alignY = l.alignY === "bottom"
|
|
108
|
+
? 1
|
|
109
|
+
: l.alignY === "center"
|
|
110
|
+
? 2
|
|
111
|
+
: 0;
|
|
112
|
+
view.setUint32(o, alignX | (alignY << 8), true);
|
|
113
|
+
o += 4;
|
|
114
|
+
}
|
|
115
|
+
if (op.bg !== undefined) {
|
|
116
|
+
view.setUint32(o, op.bg, true);
|
|
117
|
+
o += 4;
|
|
118
|
+
}
|
|
119
|
+
if (op.cornerRadius) {
|
|
120
|
+
let cr = op.cornerRadius;
|
|
121
|
+
view.setUint32(o, (cr.tl ?? 0) | ((cr.tr ?? 0) << 8) | ((cr.bl ?? 0) << 16) |
|
|
122
|
+
((cr.br ?? 0) << 24), true);
|
|
123
|
+
o += 4;
|
|
124
|
+
}
|
|
125
|
+
if (op.border) {
|
|
126
|
+
let b = op.border;
|
|
127
|
+
view.setUint32(o, b.color, true);
|
|
128
|
+
o += 4;
|
|
129
|
+
// ATTR_DEFAULT sentinel (bit 31 set) means "use terminal default bg"
|
|
130
|
+
let bg = b.bg === undefined ? 0x80000000 : b.bg & 0x00FFFFFF;
|
|
131
|
+
view.setUint32(o, bg, true);
|
|
132
|
+
o += 4;
|
|
133
|
+
view.setUint32(o, (b.left ?? 0) | ((b.right ?? 0) << 8) | ((b.top ?? 0) << 16) |
|
|
134
|
+
((b.bottom ?? 0) << 24), true);
|
|
135
|
+
o += 4;
|
|
136
|
+
}
|
|
137
|
+
if (op.clip) {
|
|
138
|
+
view.setUint32(o, (op.clip.horizontal ? 1 : 0) | ((op.clip.vertical ? 1 : 0) << 8), true);
|
|
139
|
+
o += 4;
|
|
140
|
+
}
|
|
141
|
+
if (op.floating) {
|
|
142
|
+
let f = op.floating;
|
|
143
|
+
view.setFloat32(o, f.x ?? 0, true);
|
|
144
|
+
o += 4;
|
|
145
|
+
view.setFloat32(o, f.y ?? 0, true);
|
|
146
|
+
o += 4;
|
|
147
|
+
view.setFloat32(o, f.expand?.width ?? 0, true);
|
|
148
|
+
o += 4;
|
|
149
|
+
view.setFloat32(o, f.expand?.height ?? 0, true);
|
|
150
|
+
o += 4;
|
|
151
|
+
view.setUint32(o, f.parent ?? 0, true);
|
|
152
|
+
o += 4;
|
|
153
|
+
view.setUint32(o, encodeAttachTo(f.attachTo) |
|
|
154
|
+
(encodeAttachPoint(f.attachPoints?.element) << 8) |
|
|
155
|
+
(encodeAttachPoint(f.attachPoints?.parent) << 16) |
|
|
156
|
+
(encodePointerCaptureMode(f.pointerCaptureMode) << 24), true);
|
|
157
|
+
o += 4;
|
|
158
|
+
view.setUint32(o, encodeClipTo(f.clipTo) | (((f.zIndex ?? 0) & 0xffff) << 8), true);
|
|
159
|
+
o += 4;
|
|
160
|
+
}
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
case OP_SNAPSHOT: {
|
|
164
|
+
new Uint8Array(mem).set(op.data, o);
|
|
165
|
+
o += op.data.length;
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
case OP_TEXT: {
|
|
169
|
+
view.setUint32(o, OP_TEXT, true);
|
|
170
|
+
o += 4;
|
|
171
|
+
// No explicit color: leave the terminal default foreground by writing
|
|
172
|
+
// 0 and setting ATTR_DEFAULT (0x80 in the attrs byte). The C path ORs
|
|
173
|
+
// it into fg and emit_attr skips the foreground SGR (mirrors unset bg).
|
|
174
|
+
let textDefault = op.color === undefined;
|
|
175
|
+
view.setUint32(o, op.color ?? 0, true);
|
|
176
|
+
o += 4;
|
|
177
|
+
// No explicit bg: leave the terminal default bg by writing
|
|
178
|
+
// 0 and setting ATTR_DEFAULT (0x80 in the attrs byte). The C path ORs
|
|
179
|
+
// it into bg and emit_attr skips the background SGR
|
|
180
|
+
let bg = op.bg === undefined ? 0x80000000 : op.bg & 0x00FFFFFF;
|
|
181
|
+
view.setUint32(o, bg, true);
|
|
182
|
+
o += 4;
|
|
183
|
+
view.setUint32(o, (op.fontSize ?? 1) |
|
|
184
|
+
((op.fontId ?? 0) << 8) |
|
|
185
|
+
((op.wrap ?? 0) << 16) |
|
|
186
|
+
(((op.attrs ?? 0) | (textDefault ? 0x80 : 0)) << 24), true);
|
|
187
|
+
o += 4;
|
|
188
|
+
let str = encoder.encode(op.content);
|
|
189
|
+
o = packString(view, str, o, end, "text content");
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (o > end) {
|
|
194
|
+
throw new RangeError(`ops exceed buffer capacity (${o - offset} bytes packed, ${end - offset} available)`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return (o - offset) / 4;
|
|
198
|
+
}
|
|
199
|
+
export function rgba(r, g, b, a = 255) {
|
|
200
|
+
return ((a & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) |
|
|
201
|
+
(b & 0xFF);
|
|
202
|
+
}
|
|
203
|
+
export const fit = (min = 0, max = 0) => ({
|
|
204
|
+
type: "fit",
|
|
205
|
+
min,
|
|
206
|
+
max,
|
|
207
|
+
});
|
|
208
|
+
export const grow = (min = 0, max = 0) => ({
|
|
209
|
+
type: "grow",
|
|
210
|
+
min,
|
|
211
|
+
max,
|
|
212
|
+
});
|
|
213
|
+
export const percent = (value) => ({
|
|
214
|
+
type: "percent",
|
|
215
|
+
value,
|
|
216
|
+
});
|
|
217
|
+
export const fixed = (value) => ({ type: "fixed", value });
|
|
218
|
+
const ATTACH_POINT = {
|
|
219
|
+
"left-top": 0,
|
|
220
|
+
"left-center": 1,
|
|
221
|
+
"left-bottom": 2,
|
|
222
|
+
"center-top": 3,
|
|
223
|
+
"center-center": 4,
|
|
224
|
+
"center-bottom": 5,
|
|
225
|
+
"right-top": 6,
|
|
226
|
+
"right-center": 7,
|
|
227
|
+
"right-bottom": 8,
|
|
228
|
+
};
|
|
229
|
+
const ATTACH_TO = {
|
|
230
|
+
none: 0,
|
|
231
|
+
parent: 1,
|
|
232
|
+
element: 2,
|
|
233
|
+
root: 3,
|
|
234
|
+
};
|
|
235
|
+
const POINTER_CAPTURE_MODE = {
|
|
236
|
+
capture: 0,
|
|
237
|
+
passthrough: 1,
|
|
238
|
+
};
|
|
239
|
+
const CLIP_TO = {
|
|
240
|
+
none: 0,
|
|
241
|
+
"attached-parent": 1,
|
|
242
|
+
};
|
|
243
|
+
function encodeAttachPoint(value) {
|
|
244
|
+
return value === undefined ? 0 : ATTACH_POINT[value];
|
|
245
|
+
}
|
|
246
|
+
function encodeAttachTo(value) {
|
|
247
|
+
return value === undefined ? 0 : ATTACH_TO[value];
|
|
248
|
+
}
|
|
249
|
+
function encodePointerCaptureMode(value) {
|
|
250
|
+
return value === undefined ? 0 : POINTER_CAPTURE_MODE[value];
|
|
251
|
+
}
|
|
252
|
+
function encodeClipTo(value) {
|
|
253
|
+
return value === undefined ? 0 : CLIP_TO[value];
|
|
254
|
+
}
|
|
255
|
+
export function open(id, props = {}) {
|
|
256
|
+
return { directive: OP_OPEN_ELEMENT, id, ...props };
|
|
257
|
+
}
|
|
258
|
+
export function text(content, props = {}) {
|
|
259
|
+
return { directive: OP_TEXT, content, ...props };
|
|
260
|
+
}
|
|
261
|
+
export function close() {
|
|
262
|
+
return { directive: OP_CLOSE_ELEMENT };
|
|
263
|
+
}
|
|
264
|
+
function packSize(ops) {
|
|
265
|
+
let n = 0;
|
|
266
|
+
for (let op of ops) {
|
|
267
|
+
switch (op.directive) {
|
|
268
|
+
case OP_CLOSE_ELEMENT:
|
|
269
|
+
n += 4;
|
|
270
|
+
break;
|
|
271
|
+
case OP_SNAPSHOT:
|
|
272
|
+
n += op.data.length;
|
|
273
|
+
break;
|
|
274
|
+
case OP_OPEN_ELEMENT: {
|
|
275
|
+
n += 4; // opcode
|
|
276
|
+
n += 4 + Math.ceil(encoder.encode(op.id).length / 4) * 4; // id string
|
|
277
|
+
n += 4; // mask
|
|
278
|
+
if (op.layout)
|
|
279
|
+
n += 6 * 4 + 4 + 4 + 4; // 2 axes (3 words each) + pad + gap + align
|
|
280
|
+
if (op.bg !== undefined)
|
|
281
|
+
n += 4;
|
|
282
|
+
if (op.cornerRadius)
|
|
283
|
+
n += 4;
|
|
284
|
+
if (op.border)
|
|
285
|
+
n += 12;
|
|
286
|
+
if (op.clip)
|
|
287
|
+
n += 4;
|
|
288
|
+
// x, y, expand width/height, parent, attach/pointer, clip/z
|
|
289
|
+
if (op.floating)
|
|
290
|
+
n += 7 * 4;
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
case OP_TEXT: {
|
|
294
|
+
n += 4 + 4 + 4 + 4; // opcode + color + bg + cfg
|
|
295
|
+
n += 4 + Math.ceil(encoder.encode(op.content).length / 4) * 4; // string
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return n;
|
|
301
|
+
}
|
|
302
|
+
export function snapshot(ops) {
|
|
303
|
+
let size = packSize(ops);
|
|
304
|
+
let buf = new ArrayBuffer(size);
|
|
305
|
+
let words = pack(ops, buf, 0, size);
|
|
306
|
+
return { directive: OP_SNAPSHOT, data: new Uint8Array(buf, 0, words * 4) };
|
|
307
|
+
}
|
package/esm/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface Setting {
|
|
2
|
+
apply: Uint8Array;
|
|
3
|
+
revert: Uint8Array;
|
|
4
|
+
}
|
|
5
|
+
export declare function settings(...sequence: Setting[]): Setting;
|
|
6
|
+
export declare function alternateBuffer(options?: {
|
|
7
|
+
clear?: boolean;
|
|
8
|
+
}): Setting;
|
|
9
|
+
export declare function cursor(visible: boolean): Setting;
|
|
10
|
+
/**
|
|
11
|
+
* Save and restore cursor position using DECSC (`ESC 7`) / DECRC (`ESC 8`).
|
|
12
|
+
*
|
|
13
|
+
* @see {@link https://vt100.net/docs/vt510-rm/DECSC.html | VT510 DECSC}
|
|
14
|
+
* @see {@link https://vt100.net/docs/vt510-rm/DECRC.html | VT510 DECRC}
|
|
15
|
+
*/
|
|
16
|
+
export declare function saveCursorPosition(): Setting;
|
|
17
|
+
export declare function progressiveInput(level: number): Setting;
|
|
18
|
+
export declare function mouseTracking(): Setting;
|