@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/README.md
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# @bomb.sh/tty
|
|
2
|
+
|
|
3
|
+
A low-level, platform-independent terminal renderer and event parser for
|
|
4
|
+
JavaScript. You can use `@bomb.sh/tty` directly, or as the foundation for your
|
|
5
|
+
own framework.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
**Declarative terminal UI** — Build terminal interfaces the same way you'd build
|
|
10
|
+
a web page. `@bomb.sh/tty` uses [Clay](https://github.com/nicbarker/clay) under
|
|
11
|
+
the hood, giving you flexbox-like layout, pointer detection, and scroll
|
|
12
|
+
containers — all rendered to the terminal as box-drawing characters and ANSI
|
|
13
|
+
escape sequences.
|
|
14
|
+
|
|
15
|
+
**Zero I/O** — `@bomb.sh/tty` never reads stdin or writes stdout. You feed it
|
|
16
|
+
bytes and get bytes back. This makes it trivially embeddable in any framework,
|
|
17
|
+
any runtime, any event loop. There are no opinions about how you do I/O, just
|
|
18
|
+
pure computation.
|
|
19
|
+
|
|
20
|
+
**Runs everywhere** — The entire engine is compiled to WebAssembly, so
|
|
21
|
+
`@bomb.sh/tty` will run anywhere JavaScript runs with no native dependencies,
|
|
22
|
+
and no build step for consumers.
|
|
23
|
+
|
|
24
|
+
### Examples
|
|
25
|
+
|
|
26
|
+
See this keyboard example and more in the [examples folder](examples/README.md).
|
|
27
|
+
This demo uses `@bomb.sh/tty` for all layout and input parsing.
|
|
28
|
+
|
|
29
|
+
#### Keyboard Events
|
|
30
|
+
|
|
31
|
+
The input parser decodes raw terminal bytes into structured events. Here you can
|
|
32
|
+
see each key event as the string "hello world" is typed.
|
|
33
|
+
|
|
34
|
+

|
|
35
|
+
|
|
36
|
+
#### Pointer Events
|
|
37
|
+
|
|
38
|
+
Here we see hover styles applied to UI elements in response to the pointer
|
|
39
|
+
state. Clay drives the hit testing; no manual coordinate math required.
|
|
40
|
+
|
|
41
|
+

|
|
42
|
+
|
|
43
|
+
## Architecture
|
|
44
|
+
|
|
45
|
+
`@bomb.sh/tty` does not do any I/O itself. On the ouput side, it converts UI
|
|
46
|
+
elements into a raw sequence of bytes and pointer events, and on the input side,
|
|
47
|
+
it converts a stream of raw bytes into structured events.
|
|
48
|
+
|
|
49
|
+
### Output
|
|
50
|
+
|
|
51
|
+
With every frame, the entire UI tree is packed into a flat byte array and sent
|
|
52
|
+
to WASM in a single call. On the C side, Clay runs layout, render commands are
|
|
53
|
+
walked into a cell buffer, and the buffer is diffed against the previous frame.
|
|
54
|
+
Only the cells that actually changed produce output. The result is an ANSI
|
|
55
|
+
escape sequence that can be written directly to stdout. One trip to WASM per
|
|
56
|
+
frame, double buffered, and only the bytes that need to change hit the output
|
|
57
|
+
stream.
|
|
58
|
+
|
|
59
|
+
Because the WASM module is pure computation with no I/O, it runs anywhere
|
|
60
|
+
WebAssembly does: Deno, Node, Bun, browsers, or any other runtime.
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
TypeScript WASM (C)
|
|
64
|
+
+---------------+ +---------------------------+
|
|
65
|
+
| | Uint32Array | |
|
|
66
|
+
| UI ops... | =============> | Clay layout |
|
|
67
|
+
| | | -> render commands |
|
|
68
|
+
+---------------+ | -> cell buffer (back) |
|
|
69
|
+
| -> diff against (front) |
|
|
70
|
+
| -> escape bytes |
|
|
71
|
+
+---------------+ | |
|
|
72
|
+
| | ANSI byte array| |
|
|
73
|
+
| stdout.write | <============= | |
|
|
74
|
+
| | | |
|
|
75
|
+
+---------------+ +---------------------------+
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Input
|
|
79
|
+
|
|
80
|
+
Raw bytes from stdin are fed into a WASM-based parser that recognizes VT/ANSI
|
|
81
|
+
escape sequences, UTF-8 codepoints, and mouse protocols (VT200, SGR, urxvt). The
|
|
82
|
+
parser maintains its own internal buffer so partial sequences that arrive across
|
|
83
|
+
read boundaries are reassembled automatically. A lone ESC byte is held for a
|
|
84
|
+
configurable latency window (default 25ms) before being emitted, giving
|
|
85
|
+
multi-byte sequences time to arrive.
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
TypeScript WASM (C)
|
|
89
|
+
+---------------+ +---------------------------+
|
|
90
|
+
| | raw byte array| |
|
|
91
|
+
| stdin.read | =============> | trie match (keys/seqs) |
|
|
92
|
+
| | | -> mouse protocol |
|
|
93
|
+
| | | -> UTF-8 decode |
|
|
94
|
+
+---------------+ | -> ESC codes |
|
|
95
|
+
| |
|
|
96
|
+
+---------------+ | |
|
|
97
|
+
| | events[] | |
|
|
98
|
+
| KeyEvent | <============= | |
|
|
99
|
+
| MouseDownEvent| | |
|
|
100
|
+
| MouseUpEvent | +---------------------------+
|
|
101
|
+
| MouseMoveEvent|
|
|
102
|
+
| WheelEvent |
|
|
103
|
+
| ResizeEvent |
|
|
104
|
+
+---------------+
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Usage
|
|
108
|
+
|
|
109
|
+
### Rendering
|
|
110
|
+
|
|
111
|
+
To render this:
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
╭───────────────╮
|
|
115
|
+
│ Hello, World! │
|
|
116
|
+
╰───────────────╯
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
import { close, createTerm, grow, open, rgba, text } from "@bomb.sh/tty";
|
|
121
|
+
|
|
122
|
+
let term = await createTerm({ width: 80, height: 24 });
|
|
123
|
+
|
|
124
|
+
let { output } = term.render([
|
|
125
|
+
open("root", {
|
|
126
|
+
layout: { width: grow(), height: grow(), direction: "ttb" },
|
|
127
|
+
}),
|
|
128
|
+
open("greeting", {
|
|
129
|
+
layout: { padding: { left: 1, right: 1 } },
|
|
130
|
+
border: {
|
|
131
|
+
color: rgba(0, 255, 0),
|
|
132
|
+
left: 1,
|
|
133
|
+
right: 1,
|
|
134
|
+
top: 1,
|
|
135
|
+
bottom: 1,
|
|
136
|
+
},
|
|
137
|
+
cornerRadius: { tl: 1, tr: 1, bl: 1, br: 1 },
|
|
138
|
+
}),
|
|
139
|
+
text("Hello, World!"),
|
|
140
|
+
close(),
|
|
141
|
+
close(),
|
|
142
|
+
]);
|
|
143
|
+
|
|
144
|
+
process.stdout.write(output);
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Pointer detection
|
|
148
|
+
|
|
149
|
+
Pass pointer state to `render()` to have `@bomb.sh/tty` do hit detection and
|
|
150
|
+
return pointer events in addition to the byte sequence.
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
let { output, events } = term.render(
|
|
154
|
+
[
|
|
155
|
+
open("root", {
|
|
156
|
+
layout: { width: grow(), height: grow(), direction: "ltr" },
|
|
157
|
+
}),
|
|
158
|
+
open("sidebar", {
|
|
159
|
+
layout: { width: fixed(20), height: grow() },
|
|
160
|
+
bg: rgba(30, 30, 40),
|
|
161
|
+
}),
|
|
162
|
+
text("Sidebar"),
|
|
163
|
+
close(),
|
|
164
|
+
open("main", {
|
|
165
|
+
layout: { width: grow(), height: grow() },
|
|
166
|
+
}),
|
|
167
|
+
text("Main content"),
|
|
168
|
+
close(),
|
|
169
|
+
close(),
|
|
170
|
+
],
|
|
171
|
+
{
|
|
172
|
+
pointer: { x: mouseX, y: mouseY, down: mouseDown },
|
|
173
|
+
},
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
for (let event of events) {
|
|
177
|
+
// { type: "pointerenter", id: "sidebar" }
|
|
178
|
+
// { type: "pointerleave", id: "sidebar" }
|
|
179
|
+
// { type: "pointerclick", id: "main" }
|
|
180
|
+
console.log(event);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
process.stdout.write(output);
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Input parsing
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
import { createInput } from "@bomb.sh/tty/input";
|
|
190
|
+
|
|
191
|
+
let input = await createInput({ escLatency: 25 });
|
|
192
|
+
|
|
193
|
+
process.stdin.setRawMode(true);
|
|
194
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
195
|
+
|
|
196
|
+
process.stdin.on("data", (buf) => {
|
|
197
|
+
clearTimeout(timer);
|
|
198
|
+
|
|
199
|
+
let { events, pending } = input.scan(new Uint8Array(buf));
|
|
200
|
+
|
|
201
|
+
for (let event of events) {
|
|
202
|
+
dispatch(event);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// if a lone ESC is pending, wait and re-scan to flush it
|
|
206
|
+
if (pending) {
|
|
207
|
+
timer = setTimeout(() => {
|
|
208
|
+
let flush = input.scan();
|
|
209
|
+
for (let event of flush.events) {
|
|
210
|
+
dispatch(event);
|
|
211
|
+
}
|
|
212
|
+
}, pending.delay);
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Development
|
|
218
|
+
|
|
219
|
+
For local source builds, toolchain setup, and `clay` submodule instructions, see
|
|
220
|
+
[BUILD.md](BUILD.md).
|
|
221
|
+
|
|
222
|
+
Quick local validation:
|
|
223
|
+
|
|
224
|
+
```sh
|
|
225
|
+
make
|
|
226
|
+
deno task test
|
|
227
|
+
```
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
export declare const EVENT_KEY = 1;
|
|
2
|
+
export declare const EVENT_MOUSE = 2;
|
|
3
|
+
export declare const EVENT_RESIZE = 3;
|
|
4
|
+
export declare const EVENT_CURSOR = 4;
|
|
5
|
+
export declare const MOD_ALT = 1;
|
|
6
|
+
export declare const MOD_CTRL = 2;
|
|
7
|
+
export declare const MOD_SHIFT = 4;
|
|
8
|
+
export declare const MOD_MOTION = 8;
|
|
9
|
+
export declare const MOD_RELEASE = 16;
|
|
10
|
+
export declare const KEY_F1 = 65535;
|
|
11
|
+
export declare const KEY_F2 = 65534;
|
|
12
|
+
export declare const KEY_F3 = 65533;
|
|
13
|
+
export declare const KEY_F4 = 65532;
|
|
14
|
+
export declare const KEY_F5 = 65531;
|
|
15
|
+
export declare const KEY_F6 = 65530;
|
|
16
|
+
export declare const KEY_F7 = 65529;
|
|
17
|
+
export declare const KEY_F8 = 65528;
|
|
18
|
+
export declare const KEY_F9 = 65527;
|
|
19
|
+
export declare const KEY_F10 = 65526;
|
|
20
|
+
export declare const KEY_F11 = 65525;
|
|
21
|
+
export declare const KEY_F12 = 65524;
|
|
22
|
+
export declare const KEY_ARROW_UP = 65523;
|
|
23
|
+
export declare const KEY_ARROW_DOWN = 65522;
|
|
24
|
+
export declare const KEY_ARROW_LEFT = 65521;
|
|
25
|
+
export declare const KEY_ARROW_RIGHT = 65520;
|
|
26
|
+
export declare const KEY_HOME = 65519;
|
|
27
|
+
export declare const KEY_END = 65518;
|
|
28
|
+
export declare const KEY_INSERT = 65517;
|
|
29
|
+
export declare const KEY_DELETE = 65516;
|
|
30
|
+
export declare const KEY_PGUP = 65515;
|
|
31
|
+
export declare const KEY_PGDN = 65514;
|
|
32
|
+
export declare const KEY_BACKTAB = 65513;
|
|
33
|
+
export declare const KEY_NUMPAD_0 = 65506;
|
|
34
|
+
export declare const KEY_NUMPAD_1 = 65505;
|
|
35
|
+
export declare const KEY_NUMPAD_2 = 65504;
|
|
36
|
+
export declare const KEY_NUMPAD_3 = 65503;
|
|
37
|
+
export declare const KEY_NUMPAD_4 = 65502;
|
|
38
|
+
export declare const KEY_NUMPAD_5 = 65501;
|
|
39
|
+
export declare const KEY_NUMPAD_6 = 65500;
|
|
40
|
+
export declare const KEY_NUMPAD_7 = 65499;
|
|
41
|
+
export declare const KEY_NUMPAD_8 = 65498;
|
|
42
|
+
export declare const KEY_NUMPAD_9 = 65497;
|
|
43
|
+
export declare const KEY_NUMPAD_DECIMAL = 65496;
|
|
44
|
+
export declare const KEY_NUMPAD_DIVIDE = 65495;
|
|
45
|
+
export declare const KEY_NUMPAD_MULTIPLY = 65494;
|
|
46
|
+
export declare const KEY_NUMPAD_SUBTRACT = 65493;
|
|
47
|
+
export declare const KEY_NUMPAD_ADD = 65492;
|
|
48
|
+
export declare const KEY_NUMPAD_ENTER = 65491;
|
|
49
|
+
export declare const KEY_NUMPAD_EQUAL = 65490;
|
|
50
|
+
export declare const KEY_SHIFT_LEFT = 65489;
|
|
51
|
+
export declare const KEY_SHIFT_RIGHT = 65488;
|
|
52
|
+
export declare const KEY_CONTROL_LEFT = 65487;
|
|
53
|
+
export declare const KEY_CONTROL_RIGHT = 65486;
|
|
54
|
+
export declare const KEY_ALT_LEFT = 65485;
|
|
55
|
+
export declare const KEY_ALT_RIGHT = 65484;
|
|
56
|
+
export declare const KEY_SUPER_LEFT = 65483;
|
|
57
|
+
export declare const KEY_SUPER_RIGHT = 65482;
|
|
58
|
+
export declare const KEY_HYPER_LEFT = 65481;
|
|
59
|
+
export declare const KEY_HYPER_RIGHT = 65480;
|
|
60
|
+
export declare const KEY_META_LEFT = 65479;
|
|
61
|
+
export declare const KEY_META_RIGHT = 65478;
|
|
62
|
+
export declare const KEY_CAPS_LOCK = 65477;
|
|
63
|
+
export declare const KEY_NUM_LOCK = 65476;
|
|
64
|
+
export declare const KEY_SCROLL_LOCK = 65475;
|
|
65
|
+
export declare const KEY_MOUSE_LEFT = 65512;
|
|
66
|
+
export declare const KEY_MOUSE_RIGHT = 65511;
|
|
67
|
+
export declare const KEY_MOUSE_MIDDLE = 65510;
|
|
68
|
+
export declare const KEY_MOUSE_RELEASE = 65509;
|
|
69
|
+
export declare const KEY_MOUSE_WHEEL_UP = 65508;
|
|
70
|
+
export declare const KEY_MOUSE_WHEEL_DOWN = 65507;
|
|
71
|
+
export declare const KEY_ESC = 27;
|
|
72
|
+
export declare const KEY_ENTER = 13;
|
|
73
|
+
export declare const KEY_TAB = 9;
|
|
74
|
+
export declare const KEY_BACKSPACE = 127;
|
|
75
|
+
export declare const KEY_SPACE = 32;
|
|
76
|
+
export interface NativeInputEvent {
|
|
77
|
+
type: number;
|
|
78
|
+
mod: number;
|
|
79
|
+
key: number;
|
|
80
|
+
ch: number;
|
|
81
|
+
x: number;
|
|
82
|
+
y: number;
|
|
83
|
+
w: number;
|
|
84
|
+
h: number;
|
|
85
|
+
action: number;
|
|
86
|
+
shifted: number;
|
|
87
|
+
base: number;
|
|
88
|
+
text: number[];
|
|
89
|
+
}
|
|
90
|
+
export declare function readEvent(view: DataView, ptr: number): NativeInputEvent;
|
|
91
|
+
export interface InputNative {
|
|
92
|
+
memory: WebAssembly.Memory;
|
|
93
|
+
state: number;
|
|
94
|
+
buffer: number;
|
|
95
|
+
scan(st: number, buf: number, len: number, now: number): number;
|
|
96
|
+
count(st: number): number;
|
|
97
|
+
event(st: number, index: number): number;
|
|
98
|
+
delay(st: number): number;
|
|
99
|
+
}
|
|
100
|
+
export declare function createInputNative(escLatency: number): Promise<InputNative>;
|
|
101
|
+
export declare const MAX_TERMINFO = 32768;
|
|
102
|
+
export declare const SCAN_BUFFER_SIZE = 4096;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
export const EVENT_KEY = 1;
|
|
2
|
+
export const EVENT_MOUSE = 2;
|
|
3
|
+
export const EVENT_RESIZE = 3;
|
|
4
|
+
export const EVENT_CURSOR = 4;
|
|
5
|
+
export const MOD_ALT = 1;
|
|
6
|
+
export const MOD_CTRL = 2;
|
|
7
|
+
export const MOD_SHIFT = 4;
|
|
8
|
+
export const MOD_MOTION = 8;
|
|
9
|
+
export const MOD_RELEASE = 16;
|
|
10
|
+
export const KEY_F1 = 0xFFFF;
|
|
11
|
+
export const KEY_F2 = 0xFFFE;
|
|
12
|
+
export const KEY_F3 = 0xFFFD;
|
|
13
|
+
export const KEY_F4 = 0xFFFC;
|
|
14
|
+
export const KEY_F5 = 0xFFFB;
|
|
15
|
+
export const KEY_F6 = 0xFFFA;
|
|
16
|
+
export const KEY_F7 = 0xFFF9;
|
|
17
|
+
export const KEY_F8 = 0xFFF8;
|
|
18
|
+
export const KEY_F9 = 0xFFF7;
|
|
19
|
+
export const KEY_F10 = 0xFFF6;
|
|
20
|
+
export const KEY_F11 = 0xFFF5;
|
|
21
|
+
export const KEY_F12 = 0xFFF4;
|
|
22
|
+
export const KEY_ARROW_UP = 0xFFF3;
|
|
23
|
+
export const KEY_ARROW_DOWN = 0xFFF2;
|
|
24
|
+
export const KEY_ARROW_LEFT = 0xFFF1;
|
|
25
|
+
export const KEY_ARROW_RIGHT = 0xFFF0;
|
|
26
|
+
export const KEY_HOME = 0xFFEF;
|
|
27
|
+
export const KEY_END = 0xFFEE;
|
|
28
|
+
export const KEY_INSERT = 0xFFED;
|
|
29
|
+
export const KEY_DELETE = 0xFFEC;
|
|
30
|
+
export const KEY_PGUP = 0xFFEB;
|
|
31
|
+
export const KEY_PGDN = 0xFFEA;
|
|
32
|
+
export const KEY_BACKTAB = 0xFFE9;
|
|
33
|
+
export const KEY_NUMPAD_0 = 0xFFE2;
|
|
34
|
+
export const KEY_NUMPAD_1 = 0xFFE1;
|
|
35
|
+
export const KEY_NUMPAD_2 = 0xFFE0;
|
|
36
|
+
export const KEY_NUMPAD_3 = 0xFFDF;
|
|
37
|
+
export const KEY_NUMPAD_4 = 0xFFDE;
|
|
38
|
+
export const KEY_NUMPAD_5 = 0xFFDD;
|
|
39
|
+
export const KEY_NUMPAD_6 = 0xFFDC;
|
|
40
|
+
export const KEY_NUMPAD_7 = 0xFFDB;
|
|
41
|
+
export const KEY_NUMPAD_8 = 0xFFDA;
|
|
42
|
+
export const KEY_NUMPAD_9 = 0xFFD9;
|
|
43
|
+
export const KEY_NUMPAD_DECIMAL = 0xFFD8;
|
|
44
|
+
export const KEY_NUMPAD_DIVIDE = 0xFFD7;
|
|
45
|
+
export const KEY_NUMPAD_MULTIPLY = 0xFFD6;
|
|
46
|
+
export const KEY_NUMPAD_SUBTRACT = 0xFFD5;
|
|
47
|
+
export const KEY_NUMPAD_ADD = 0xFFD4;
|
|
48
|
+
export const KEY_NUMPAD_ENTER = 0xFFD3;
|
|
49
|
+
export const KEY_NUMPAD_EQUAL = 0xFFD2;
|
|
50
|
+
export const KEY_SHIFT_LEFT = 0xFFD1;
|
|
51
|
+
export const KEY_SHIFT_RIGHT = 0xFFD0;
|
|
52
|
+
export const KEY_CONTROL_LEFT = 0xFFCF;
|
|
53
|
+
export const KEY_CONTROL_RIGHT = 0xFFCE;
|
|
54
|
+
export const KEY_ALT_LEFT = 0xFFCD;
|
|
55
|
+
export const KEY_ALT_RIGHT = 0xFFCC;
|
|
56
|
+
export const KEY_SUPER_LEFT = 0xFFCB;
|
|
57
|
+
export const KEY_SUPER_RIGHT = 0xFFCA;
|
|
58
|
+
export const KEY_HYPER_LEFT = 0xFFC9;
|
|
59
|
+
export const KEY_HYPER_RIGHT = 0xFFC8;
|
|
60
|
+
export const KEY_META_LEFT = 0xFFC7;
|
|
61
|
+
export const KEY_META_RIGHT = 0xFFC6;
|
|
62
|
+
export const KEY_CAPS_LOCK = 0xFFC5;
|
|
63
|
+
export const KEY_NUM_LOCK = 0xFFC4;
|
|
64
|
+
export const KEY_SCROLL_LOCK = 0xFFC3;
|
|
65
|
+
export const KEY_MOUSE_LEFT = 0xFFE8;
|
|
66
|
+
export const KEY_MOUSE_RIGHT = 0xFFE7;
|
|
67
|
+
export const KEY_MOUSE_MIDDLE = 0xFFE6;
|
|
68
|
+
export const KEY_MOUSE_RELEASE = 0xFFE5;
|
|
69
|
+
export const KEY_MOUSE_WHEEL_UP = 0xFFE4;
|
|
70
|
+
export const KEY_MOUSE_WHEEL_DOWN = 0xFFE3;
|
|
71
|
+
export const KEY_ESC = 0x1B;
|
|
72
|
+
export const KEY_ENTER = 0x0D;
|
|
73
|
+
export const KEY_TAB = 0x09;
|
|
74
|
+
export const KEY_BACKSPACE = 0x7F;
|
|
75
|
+
export const KEY_SPACE = 0x20;
|
|
76
|
+
import { array, int32, offsets, struct, uint16, uint32, uint8, } from "./typedef.js";
|
|
77
|
+
const MAX_TEXT_CODEPOINTS = 8;
|
|
78
|
+
const InputEventLayout = struct({
|
|
79
|
+
type: uint8(),
|
|
80
|
+
mod: uint8(),
|
|
81
|
+
key: uint16(),
|
|
82
|
+
ch: uint32(),
|
|
83
|
+
x: int32(),
|
|
84
|
+
y: int32(),
|
|
85
|
+
w: int32(),
|
|
86
|
+
h: int32(),
|
|
87
|
+
action: uint8(),
|
|
88
|
+
shifted: uint32(),
|
|
89
|
+
base: uint32(),
|
|
90
|
+
text: array(uint32(), MAX_TEXT_CODEPOINTS),
|
|
91
|
+
text_len: uint8(),
|
|
92
|
+
});
|
|
93
|
+
const { type: OFFSET_TYPE, mod: OFFSET_MOD, key: OFFSET_KEY, ch: OFFSET_CH, x: OFFSET_X, y: OFFSET_Y, w: OFFSET_W, h: OFFSET_H, action: OFFSET_ACTION, text_len: OFFSET_TEXT_LEN, shifted: OFFSET_SHIFTED, base: OFFSET_BASE, text: OFFSET_TEXT, } = offsets(InputEventLayout);
|
|
94
|
+
export function readEvent(view, ptr) {
|
|
95
|
+
let len = view.getUint8(ptr + OFFSET_TEXT_LEN);
|
|
96
|
+
let text = [];
|
|
97
|
+
for (let i = 0; i < len && i < MAX_TEXT_CODEPOINTS; i++) {
|
|
98
|
+
text.push(view.getUint32(ptr + OFFSET_TEXT + i * 4, true));
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
type: view.getUint8(ptr + OFFSET_TYPE),
|
|
102
|
+
mod: view.getUint8(ptr + OFFSET_MOD),
|
|
103
|
+
key: view.getUint16(ptr + OFFSET_KEY, true),
|
|
104
|
+
ch: view.getUint32(ptr + OFFSET_CH, true),
|
|
105
|
+
x: view.getInt32(ptr + OFFSET_X, true),
|
|
106
|
+
y: view.getInt32(ptr + OFFSET_Y, true),
|
|
107
|
+
w: view.getInt32(ptr + OFFSET_W, true),
|
|
108
|
+
h: view.getInt32(ptr + OFFSET_H, true),
|
|
109
|
+
action: view.getUint8(ptr + OFFSET_ACTION),
|
|
110
|
+
shifted: view.getUint32(ptr + OFFSET_SHIFTED, true),
|
|
111
|
+
base: view.getUint32(ptr + OFFSET_BASE, true),
|
|
112
|
+
text,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
import { compiled } from "./wasm.js";
|
|
116
|
+
export async function createInputNative(escLatency) {
|
|
117
|
+
let memory = new WebAssembly.Memory({ initial: 4 });
|
|
118
|
+
let instance = await WebAssembly.instantiate(compiled, {
|
|
119
|
+
env: { memory },
|
|
120
|
+
clay: {
|
|
121
|
+
measureTextFunction() { },
|
|
122
|
+
queryScrollOffsetFunction(ret) {
|
|
123
|
+
let v = new DataView(memory.buffer);
|
|
124
|
+
v.setFloat32(ret, 0, true);
|
|
125
|
+
v.setFloat32(ret + 4, 0, true);
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
let exports = instance.exports;
|
|
130
|
+
let heap = exports.__heap_base.value;
|
|
131
|
+
let size = exports.input_size();
|
|
132
|
+
let state = exports.input_init(heap, escLatency);
|
|
133
|
+
let buffer = (heap + size + 7) & ~7;
|
|
134
|
+
return {
|
|
135
|
+
memory,
|
|
136
|
+
state,
|
|
137
|
+
buffer,
|
|
138
|
+
scan: exports.input_scan,
|
|
139
|
+
count: exports.input_count,
|
|
140
|
+
event: exports.input_event,
|
|
141
|
+
delay: exports.input_delay,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
// Compiled terminfo entries are limited to 4096 bytes (legacy) or 32768
|
|
145
|
+
// bytes (extended ncurses format). We use the extended limit as our upper
|
|
146
|
+
// bound. See https://man7.org/linux/man-pages/man5/term.5.html
|
|
147
|
+
export const MAX_TERMINFO = 32768;
|
|
148
|
+
// Must match SCAN_BUFFER_SIZE in input.c — the maximum bytes input_scan()
|
|
149
|
+
// can accept in a single call.
|
|
150
|
+
export const SCAN_BUFFER_SIZE = 4096;
|
package/esm/input.d.ts
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
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
|
+
/**
|
|
9
|
+
* Modifier keys held during a key or mouse event.
|
|
10
|
+
*/
|
|
11
|
+
export interface KeyModifiers {
|
|
12
|
+
alt?: true;
|
|
13
|
+
ctrl?: true;
|
|
14
|
+
shift?: true;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Physical key identity on a US PC-101 layout.
|
|
18
|
+
*/
|
|
19
|
+
export type KeyCode = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z" | "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "`" | "-" | "=" | "[" | "]" | "\\" | ";" | "'" | "," | "." | "/" | " " | "F1" | "F2" | "F3" | "F4" | "F5" | "F6" | "F7" | "F8" | "F9" | "F10" | "F11" | "F12" | "ArrowUp" | "ArrowDown" | "ArrowLeft" | "ArrowRight" | "Home" | "End" | "Insert" | "Delete" | "PageUp" | "PageDown" | "Backtab" | "Backspace" | "Tab" | "Enter" | "Escape" | "Numpad0" | "Numpad1" | "Numpad2" | "Numpad3" | "Numpad4" | "Numpad5" | "Numpad6" | "Numpad7" | "Numpad8" | "Numpad9" | "NumpadDecimal" | "NumpadDivide" | "NumpadMultiply" | "NumpadSubtract" | "NumpadAdd" | "NumpadEnter" | "NumpadEqual" | "ShiftLeft" | "ShiftRight" | "ControlLeft" | "ControlRight" | "AltLeft" | "AltRight" | "SuperLeft" | "SuperRight" | "HyperLeft" | "HyperRight" | "MetaLeft" | "MetaRight" | "CapsLock" | "NumLock" | "ScrollLock";
|
|
20
|
+
/**
|
|
21
|
+
* Shared key information present on all keyboard events.
|
|
22
|
+
*/
|
|
23
|
+
export interface KeyInfo extends KeyModifiers {
|
|
24
|
+
key: string;
|
|
25
|
+
code: KeyCode;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* A key was pressed. Emitted at all enhancement levels.
|
|
29
|
+
* On legacy terminals, this is the only keyboard event type.
|
|
30
|
+
*/
|
|
31
|
+
export interface KeyDown extends KeyInfo {
|
|
32
|
+
type: "keydown";
|
|
33
|
+
shifted?: string;
|
|
34
|
+
text?: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* A key is being held down (auto-repeat). Only emitted with
|
|
38
|
+
* Kitty enhancement level 2+ (report event types).
|
|
39
|
+
*/
|
|
40
|
+
export interface KeyRepeat extends KeyInfo {
|
|
41
|
+
type: "keyrepeat";
|
|
42
|
+
shifted?: string;
|
|
43
|
+
text?: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* A key was released. Only emitted with Kitty enhancement
|
|
47
|
+
* level 2+ (report event types). Does not carry text,
|
|
48
|
+
* shifted, or base fields.
|
|
49
|
+
*/
|
|
50
|
+
export interface KeyUp extends KeyInfo {
|
|
51
|
+
type: "keyup";
|
|
52
|
+
}
|
|
53
|
+
export type KeyEvent = KeyDown | KeyRepeat | KeyUp;
|
|
54
|
+
/**
|
|
55
|
+
* A mouse button was pressed.
|
|
56
|
+
*/
|
|
57
|
+
export interface MouseDownEvent extends KeyModifiers {
|
|
58
|
+
type: "mousedown";
|
|
59
|
+
/**
|
|
60
|
+
* Which mouse button triggered this event.
|
|
61
|
+
*/
|
|
62
|
+
button: "left" | "right" | "middle";
|
|
63
|
+
/**
|
|
64
|
+
* `x` coordinate of this event
|
|
65
|
+
*/
|
|
66
|
+
x: number;
|
|
67
|
+
/**
|
|
68
|
+
* `y` coordinate of this event
|
|
69
|
+
*/
|
|
70
|
+
y: number;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* A mouse button was released.
|
|
74
|
+
*/
|
|
75
|
+
export interface MouseUpEvent extends KeyModifiers {
|
|
76
|
+
type: "mouseup";
|
|
77
|
+
/**
|
|
78
|
+
* Which mouse button triggered this event.
|
|
79
|
+
*
|
|
80
|
+
* Note: "release" is not technically a button, but is used by
|
|
81
|
+
* `VT200` and `urxvt` protocols where the terminal reports a button
|
|
82
|
+
* release without indicating which button.
|
|
83
|
+
*/
|
|
84
|
+
button: "left" | "right" | "middle" | "release";
|
|
85
|
+
/**
|
|
86
|
+
* `x` coordinate of this event
|
|
87
|
+
*/
|
|
88
|
+
x: number;
|
|
89
|
+
/**
|
|
90
|
+
* `y` coordinate of this event
|
|
91
|
+
*/
|
|
92
|
+
y: number;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Mouse movement while a button is held.
|
|
96
|
+
*/
|
|
97
|
+
export interface MouseMoveEvent extends KeyModifiers {
|
|
98
|
+
type: "mousemove";
|
|
99
|
+
/**
|
|
100
|
+
* Which mouse button is being held during the drag.
|
|
101
|
+
*/
|
|
102
|
+
button: "left" | "right" | "middle";
|
|
103
|
+
/**
|
|
104
|
+
* Cursor column (0-based).
|
|
105
|
+
*/
|
|
106
|
+
x: number;
|
|
107
|
+
/**
|
|
108
|
+
* Cursor row (0-based).
|
|
109
|
+
*/
|
|
110
|
+
y: number;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* A scroll wheel tick.
|
|
114
|
+
*/
|
|
115
|
+
export interface WheelEvent extends KeyModifiers {
|
|
116
|
+
type: "wheel";
|
|
117
|
+
/**
|
|
118
|
+
* Did the wheel move up or down
|
|
119
|
+
*/
|
|
120
|
+
direction: "up" | "down";
|
|
121
|
+
/**
|
|
122
|
+
* Cursor column at the time of the scroll (0-based).
|
|
123
|
+
*/
|
|
124
|
+
x: number;
|
|
125
|
+
/**
|
|
126
|
+
* Cursor row at the time of the scroll (0-based).
|
|
127
|
+
*/
|
|
128
|
+
y: number;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Terminal resize notification.
|
|
132
|
+
*/
|
|
133
|
+
export interface ResizeEvent {
|
|
134
|
+
type: "resize";
|
|
135
|
+
/**
|
|
136
|
+
* New terminal width in columns.
|
|
137
|
+
*/
|
|
138
|
+
width: number;
|
|
139
|
+
/**
|
|
140
|
+
* New terminal height in rows.
|
|
141
|
+
*/
|
|
142
|
+
height: number;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Cursor position report (DSR response).
|
|
146
|
+
*
|
|
147
|
+
* Emitted when the terminal responds to a Device Status Report
|
|
148
|
+
* query (`\x1b[6n`) with the current cursor position.
|
|
149
|
+
*/
|
|
150
|
+
export interface CursorEvent {
|
|
151
|
+
type: "cursor";
|
|
152
|
+
/**
|
|
153
|
+
* Cursor row (1-based). Matches ECMA-48 DSR native format.
|
|
154
|
+
*/
|
|
155
|
+
row: number;
|
|
156
|
+
/**
|
|
157
|
+
* Cursor column (1-based). Matches ECMA-48 DSR native format.
|
|
158
|
+
*/
|
|
159
|
+
column: number;
|
|
160
|
+
}
|
|
161
|
+
import type { PointerEvent } from "./term.js";
|
|
162
|
+
export type InputEvent = KeyEvent | MouseDownEvent | MouseUpEvent | MouseMoveEvent | WheelEvent | ResizeEvent | CursorEvent | PointerEvent;
|
|
163
|
+
/**
|
|
164
|
+
* Result of a single scan() call.
|
|
165
|
+
*
|
|
166
|
+
* When `pending` is present, a lone ESC is buffered and the caller should
|
|
167
|
+
* re-call scan() with an empty buffer after `pending.delay` milliseconds.
|
|
168
|
+
*/
|
|
169
|
+
export interface ScanResult {
|
|
170
|
+
events: InputEvent[];
|
|
171
|
+
pending?: {
|
|
172
|
+
delay: number;
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
export interface Input {
|
|
176
|
+
/**
|
|
177
|
+
* Feed raw bytes from stdin into the parser and return any events
|
|
178
|
+
* produced. Call with no arguments to flush a pending ESC after the
|
|
179
|
+
* latency period has elapsed.
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* ```ts
|
|
183
|
+
* let { events, pending } = input.scan(bytes);
|
|
184
|
+
* for (let event of events) {
|
|
185
|
+
* dispatch(event);
|
|
186
|
+
* }
|
|
187
|
+
* if (pending) {
|
|
188
|
+
* // there is a pending ESC event. wait for the delay
|
|
189
|
+
* await sleep(pending.delay);
|
|
190
|
+
*
|
|
191
|
+
* // re-scan
|
|
192
|
+
* let flush = input.scan();
|
|
193
|
+
*
|
|
194
|
+
* //dispatch the flushed ESC
|
|
195
|
+
* for (let event of flush.events) {
|
|
196
|
+
* dispatch(event)
|
|
197
|
+
* }
|
|
198
|
+
* }
|
|
199
|
+
* ```
|
|
200
|
+
*/
|
|
201
|
+
scan(bytes?: Uint8Array): ScanResult;
|
|
202
|
+
}
|
|
203
|
+
export interface InputOptions {
|
|
204
|
+
/**
|
|
205
|
+
* Milliseconds to wait before resolving a lone ESC byte as the Escape
|
|
206
|
+
* key rather than the start of an escape sequence. Lower values feel
|
|
207
|
+
* snappier but risk misinterpreting sequences on slow connections.
|
|
208
|
+
*
|
|
209
|
+
* For reference, Vim's `ttimeoutlen` defaults to 100ms and ncurses
|
|
210
|
+
* `ESCDELAY` defaults to 1000ms. The default of 25ms is tuned for
|
|
211
|
+
* local terminals where escape sequences arrive within microseconds.
|
|
212
|
+
*
|
|
213
|
+
* @default 25
|
|
214
|
+
*/
|
|
215
|
+
escLatency?: number;
|
|
216
|
+
/**
|
|
217
|
+
* Compiled terminfo binary to load terminal-specific escape sequences.
|
|
218
|
+
*
|
|
219
|
+
* This is the format used by files like /usr/lib/terminfo/78/xterm-256color
|
|
220
|
+
* and they can be directly loaded from disk into this option.
|
|
221
|
+
*
|
|
222
|
+
* If no terminfo is provided it will use xterm capabilities as the default
|
|
223
|
+
*/
|
|
224
|
+
terminfo?: Uint8Array;
|
|
225
|
+
}
|
|
226
|
+
export declare function createInput(options?: InputOptions): Promise<Input>;
|