@amritk/nish-x86_64-linux 0.20.0 → 0.21.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/INSTALL.md +22 -10
- package/bin/nish +0 -0
- package/docs/AI.md +2759 -0
- package/package.json +2 -1
- package/runtime/browser.d.ts +14 -0
- package/runtime/browser.mjs +88 -0
- package/runtime/core/arrays.d.ts +18 -0
- package/runtime/core/arrays.mjs +125 -0
- package/runtime/core/checks.mjs +20 -0
- package/runtime/core/host.d.ts +70 -0
- package/runtime/core/host.mjs +311 -0
- package/runtime/core/numbers.d.ts +53 -0
- package/runtime/core/numbers.mjs +316 -0
- package/runtime/core/result.d.ts +41 -0
- package/runtime/core/result.mjs +127 -0
- package/runtime/core/strings.d.ts +14 -0
- package/runtime/core/strings.mjs +169 -0
- package/runtime/core.d.ts +9 -0
- package/runtime/core.mjs +55 -0
- package/runtime/host.d.ts +6 -0
- package/runtime/nish.d.ts +44 -0
- package/runtime/nish.h +75 -2
- package/runtime/nish.mjs +23 -3
- package/runtime/node.mjs +383 -0
- package/runtime/runtime-bytes.c +401 -0
- package/runtime/runtime-cross.c +1 -1
- package/runtime/runtime-host.c +1 -1
- package/runtime/runtime-os.c +1 -1
- package/runtime/runtime-parallel.c +1 -1
- package/runtime/runtime-simd.c +231 -7
- package/runtime/runtime-term.c +504 -0
- package/runtime/runtime-wasm.c +0 -11
- package/runtime/runtime.c +190 -6
- package/runtime/shim.mjs +6 -985
- package/runtime/sqlite/LICENSE.md +85 -0
- package/runtime/sqlite/README.md +45 -0
- package/runtime/sqlite/nish-sqlite.c +359 -0
- package/runtime/sqlite/sqlite3.c +271131 -0
- package/runtime/sqlite/sqlite3.h +14447 -0
- package/scripts/build.sh +261 -53
- package/std/LICENSE-fast-float +23 -0
- package/std/LICENSE-fdlibm +8 -0
- package/std/README.md +31 -11
- package/std/base64.ts +116 -0
- package/std/bytes.ts +256 -0
- package/std/cli.ts +328 -0
- package/std/collections.ts +6 -5
- package/std/crypto/LICENSE-crypt_blowfish +26 -0
- package/std/crypto/base64url.ts +125 -0
- package/std/crypto/bcrypt.ts +760 -0
- package/std/crypto/hmac.ts +138 -2
- package/std/crypto/x509.ts +11 -122
- package/std/json-tokenizer.ts +2998 -57
- package/std/json-value.ts +22 -3
- package/std/json.ts +83 -1
- package/std/math.ts +608 -0
- package/std/net/http1-server.ts +85 -8
- package/std/net/http3-server.ts +50 -12
- package/std/net/postgres.ts +1875 -0
- package/std/net/quic-listener.ts +20 -4
- package/std/regex.ts +4274 -0
- package/std/sqlite.ts +391 -0
- package/std/term-io.ts +978 -0
- package/std/term-pixels.ts +874 -0
- package/std/term-probe.ts +459 -0
- package/std/term-rate.ts +993 -0
- package/std/term.ts +2453 -0
- package/std/text.ts +90 -7
- package/std/yaml-lines.ts +141 -0
- package/std/yaml.ts +4675 -0
package/std/term.ts
ADDED
|
@@ -0,0 +1,2453 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `std/term` — a terminal drawn as a grid of coloured cells, and the keys a
|
|
3
|
+
* terminal sends turned into events.
|
|
4
|
+
*
|
|
5
|
+
* import { Screen, Canvas, rgb, COLOR_DEFAULT, enterAltScreen, hideCursor } from "nish/term";
|
|
6
|
+
*
|
|
7
|
+
* const screen = new Screen(80, 24, false);
|
|
8
|
+
* write(enterAltScreen() + hideCursor());
|
|
9
|
+
* screen.clear(COLOR_DEFAULT);
|
|
10
|
+
* screen.text(2, 1, "score 0", rgb(255, 255, 0), COLOR_DEFAULT, 0);
|
|
11
|
+
* write(screen.flush()); // the whole frame the first time, then only what changed
|
|
12
|
+
*
|
|
13
|
+
* **No I/O.** Nothing here reads the terminal or writes to it: a `Screen` turns
|
|
14
|
+
* the cells a program drew into the string that brings the terminal from the
|
|
15
|
+
* last frame to this one, and a `KeyDecoder` turns the bytes a program read
|
|
16
|
+
* into key events. The program writes the string with `write` and reads the
|
|
17
|
+
* bytes however it reads its input; the size of the terminal, raw mode and the
|
|
18
|
+
* clock are the caller's, passed in as numbers. So this module reaches no
|
|
19
|
+
* capability, a test can compare its output byte for byte, and it runs the
|
|
20
|
+
* same under Node.
|
|
21
|
+
*
|
|
22
|
+
* **A `Screen`** holds `width × height` cells, each a code point, a foreground,
|
|
23
|
+
* a background and two attributes (bold, inverse), twice over: the frame being
|
|
24
|
+
* drawn and the frame the terminal was last sent. `flush` walks both and emits
|
|
25
|
+
* only the cells that differ — a cursor move where it is not already in place,
|
|
26
|
+
* a style change only where the style differs from the cell before it, and the
|
|
27
|
+
* glyph's UTF-8 bytes — then makes the drawn frame the sent one. Every glyph is
|
|
28
|
+
* taken to be one column wide; a wide character (most CJK, most emoji) would
|
|
29
|
+
* push the rest of its row along, and is the caller's to avoid.
|
|
30
|
+
*
|
|
31
|
+
* **A `Canvas`** draws pixels on a `Screen`: each cell shows two, one above the
|
|
32
|
+
* other, with the upper half block "▀" in the top pixel's colour on a background
|
|
33
|
+
* of the bottom pixel's (and the lower half block "▄" when only the bottom
|
|
34
|
+
* pixel is set). A terminal cell is about twice as tall as it is wide, so the
|
|
35
|
+
* pixels come out roughly square.
|
|
36
|
+
*
|
|
37
|
+
* **Colour** is 24-bit RGB, an `i32` `0xRRGGBB` made by `rgb`, or
|
|
38
|
+
* `COLOR_DEFAULT` for the terminal's own colour. A `Screen` made with
|
|
39
|
+
* `palette256` set sends each colour as the nearest of the 256-colour palette
|
|
40
|
+
* instead, for a terminal without true colour; deciding which a terminal has
|
|
41
|
+
* (`COLORTERM` is the usual sign) is the caller's.
|
|
42
|
+
*
|
|
43
|
+
* **Events.** `KeyDecoder.feed` and `poll` answer `KeyEvent`s, as they always
|
|
44
|
+
* have. `KeyDecoder.feedEvents(bytes, count, nowMs)` and `pollEvents(nowMs)`
|
|
45
|
+
* read the same bytes as one stream of `TermEvent`s, whose `type` is an
|
|
46
|
+
* `EVENT_` constant:
|
|
47
|
+
*
|
|
48
|
+
* - `EVENT_KEY`, a key with its `KEY_` kind and `code`, its `MOD_` bits
|
|
49
|
+
* (`MOD_SHIFT`, `MOD_ALT`, `MOD_CTRL`, `MOD_SUPER`, `MOD_HYPER`, `MOD_META`)
|
|
50
|
+
* and its `ACTION_` (`ACTION_PRESS`, `ACTION_REPEAT`, `ACTION_RELEASE`):
|
|
51
|
+
* legacy keys, xterm's modified keys and modifyOtherKeys
|
|
52
|
+
* (`ESC [ 27 ; m ; c ~`), and the kitty keyboard protocol's
|
|
53
|
+
* `ESC [ code ; mods:event ; text u` (sw.kovidgoyal.net/kitty/keyboard-protocol);
|
|
54
|
+
* - `EVENT_MOUSE`, an SGR report `ESC [ < b ; x ; y M|m` (xterm ctlseqs, mode
|
|
55
|
+
* 1006, or 1016 in pixels with `pixelMouse` set) with its `MOUSE_` button,
|
|
56
|
+
* `ACTION_PRESS`, `ACTION_RELEASE` or `ACTION_MOVE`, and cell `x`, `y`;
|
|
57
|
+
* - `EVENT_PASTE`, everything between `ESC [ 200 ~` and `ESC [ 201 ~` (xterm
|
|
58
|
+
* ctlseqs, "Bracketed Paste Mode") as one `text`, never as keys;
|
|
59
|
+
* - `EVENT_FOCUS`, `ESC [ I` or `ESC [ O` (mode 1004), `gained` or not;
|
|
60
|
+
* - `EVENT_REPLY`, every answer a terminal sends to a query — DA, DSR, CPR,
|
|
61
|
+
* DECRPM, the kitty flags, XTVERSION and DECRQSS (DCS), OSC, APC — with its
|
|
62
|
+
* `introducer`, `prefix`, `params`, `intermediate`, final byte `reply` and
|
|
63
|
+
* `text`. A reply never reaches the program as a key. `ESC [ row ; col R` is
|
|
64
|
+
* both a cursor position report and F3 with modifiers, so a caller that asks
|
|
65
|
+
* for one (`ESC [ 6 n`) adds 1 to `cprOutstanding`, and while it is above 0
|
|
66
|
+
* the next `R` is the reply, taking 1 off. Nothing expires it: a caller
|
|
67
|
+
* that gives up waiting for a report sets it back to 0;
|
|
68
|
+
* - `EVENT_RESIZE` and `EVENT_QUIT`, which the caller and `KeyState` make.
|
|
69
|
+
*
|
|
70
|
+
* An unfinished sequence is held for `escapeTimeoutMs` after the last byte arrived,
|
|
71
|
+
* then given up by `pollEvents` as `feed` would; a control string cut off by
|
|
72
|
+
* the timeout is dropped whole. A paste is never timed out. A CSI reply cut
|
|
73
|
+
* off by the timeout (a terminal that stalls inside `ESC [ ? 6 2 c`) cannot
|
|
74
|
+
* be told from keys, so its bytes come out as the keys they spell.
|
|
75
|
+
*
|
|
76
|
+
* **A `KeyState`** turns those events into which keys are down. `apply(events,
|
|
77
|
+
* nowMs)` answers the events to act on and `tick(nowMs)` the releases due
|
|
78
|
+
* meanwhile; `isDown(key, code)` asks. A terminal without the kitty protocol
|
|
79
|
+
* sends no releases, so while `releasesAreSynthetic` is set (until the first
|
|
80
|
+
* real release) a key counts as let go when its repeats stop: `firstRepeatMs`
|
|
81
|
+
* (700 ms) after a press that has not repeated and `releaseGapMs` (100 ms)
|
|
82
|
+
* after its last repeat. Silence is measured in stream time when the caller has
|
|
83
|
+
* one: term-io sends an in-order query after each frame and calls `delivered`
|
|
84
|
+
* with the send time of each one answered, since by then everything the
|
|
85
|
+
* terminal generated before it has arrived. Measured that way a stall, a burst
|
|
86
|
+
* or jittery reads delay the watermark as much as the keys, so a held key never
|
|
87
|
+
* flickers and a tap comes up 700 ms plus one round trip after its press. With
|
|
88
|
+
* no watermark yet, silence is wall-clock time between `apply`s, which a link
|
|
89
|
+
* that reads in bursts more than `releaseGapMs` apart can mistake for a key let
|
|
90
|
+
* go. While acks stop, nothing is released: there is no wall-clock backstop,
|
|
91
|
+
* and a caller that stops getting answers clears `watermarked` to fall back to
|
|
92
|
+
* the wall clock. term-io does so once 1000 ms pass with no query answered,
|
|
93
|
+
* frames sent or not, which no working link reaches while it draws.
|
|
94
|
+
* Within one read the caller `apply`s its events before calling `delivered` for
|
|
95
|
+
* the answer it carries: a `tick` between the two would judge keys against a
|
|
96
|
+
* watermark that already covers repeats not yet applied, and report a release
|
|
97
|
+
* and then a press. A non-finite time is ignored. Under X11 and Windows only
|
|
98
|
+
* the last key pressed auto-repeats, so holding W and then pressing D lets W go
|
|
99
|
+
* `releaseGapMs` after its last repeat. A real release of a key not down is
|
|
100
|
+
* dropped, so one arriving after its key was made up as released is not
|
|
101
|
+
* reported twice. Under kitty a real release that is lost leaves its key down
|
|
102
|
+
* until focus is lost, and for good without focus reporting. A key the batch
|
|
103
|
+
* `apply` takes names stays down. A made-up release carries the code and
|
|
104
|
+
* modifiers the key last came with. Losing focus lets every key go, and Ctrl-C
|
|
105
|
+
* becomes `EVENT_QUIT` while `quitOnCtrlC` is set. Under the kitty protocol a
|
|
106
|
+
* non-Latin layout sends Ctrl-C as its own letter (`ESC [ 1089 ; 5 u` for
|
|
107
|
+
* Russian С), which is not read as a quit: the base-layout key that flag 4
|
|
108
|
+
* (report alternate keys) adds is not read yet.
|
|
109
|
+
*
|
|
110
|
+
* **The same bytes under Node.** Natively a string is UTF-8 bytes, and `flush`
|
|
111
|
+
* builds its answer as bytes and makes one string of them. Under Node a string
|
|
112
|
+
* is UTF-16 units and `write` encodes it as UTF-8, so there `flush` decodes the
|
|
113
|
+
* same bytes into code units first: the terminal receives the same bytes either
|
|
114
|
+
* way, which `tests/run.js` checks ("nish/term prints the same bytes under
|
|
115
|
+
* runtime/nish.mjs"). `text` reads a string's code points as UTF-8 natively and
|
|
116
|
+
* as UTF-16 under Node, so a string literal draws the same cells both ways.
|
|
117
|
+
* A `TermEvent`'s `text` is made from bytes the terminal sent: natively it
|
|
118
|
+
* holds them as they came, and under Node they are decoded as UTF-8 without
|
|
119
|
+
* checking, so malformed UTF-8 in a paste or reply can differ between the two.
|
|
120
|
+
* Integer division is spelled `(a / b) | 0`, which truncates in both.
|
|
121
|
+
*/
|
|
122
|
+
|
|
123
|
+
/** The terminal's own colour, foreground or background: SGR 39 and 49. */
|
|
124
|
+
export const COLOR_DEFAULT: i32 = -1
|
|
125
|
+
|
|
126
|
+
/** No attributes: plain text. */
|
|
127
|
+
export const ATTR_NONE: i32 = 0
|
|
128
|
+
|
|
129
|
+
/** The bold attribute bit: SGR 1. */
|
|
130
|
+
export const ATTR_BOLD: i32 = 1
|
|
131
|
+
|
|
132
|
+
/** The inverse-video attribute bit, foreground and background swapped: SGR 7. */
|
|
133
|
+
export const ATTR_INVERSE: i32 = 2
|
|
134
|
+
|
|
135
|
+
/** A printable character; the event's `code` is its code point. */
|
|
136
|
+
export const KEY_CHAR: i32 = 1
|
|
137
|
+
|
|
138
|
+
/** The up arrow, `ESC [ A` or `ESC O A`. */
|
|
139
|
+
export const KEY_UP: i32 = 2
|
|
140
|
+
|
|
141
|
+
/** The down arrow, `ESC [ B` or `ESC O B`. */
|
|
142
|
+
export const KEY_DOWN: i32 = 3
|
|
143
|
+
|
|
144
|
+
/** The right arrow, `ESC [ C` or `ESC O C`. */
|
|
145
|
+
export const KEY_RIGHT: i32 = 4
|
|
146
|
+
|
|
147
|
+
/** The left arrow, `ESC [ D` or `ESC O D`. */
|
|
148
|
+
export const KEY_LEFT: i32 = 5
|
|
149
|
+
|
|
150
|
+
/** Enter: a carriage return (13) or a line feed (10), which is the byte's `code`. */
|
|
151
|
+
export const KEY_ENTER: i32 = 6
|
|
152
|
+
|
|
153
|
+
/** The space bar (32). */
|
|
154
|
+
export const KEY_SPACE: i32 = 7
|
|
155
|
+
|
|
156
|
+
/** The escape key: an ESC that no sequence followed within the timeout. */
|
|
157
|
+
export const KEY_ESCAPE: i32 = 8
|
|
158
|
+
|
|
159
|
+
/** Ctrl-C, byte 3, which a terminal in raw mode delivers instead of a signal. */
|
|
160
|
+
export const KEY_CTRL_C: i32 = 9
|
|
161
|
+
|
|
162
|
+
/** Tab (9). */
|
|
163
|
+
export const KEY_TAB: i32 = 10
|
|
164
|
+
|
|
165
|
+
/** Backspace: DEL (127) or BS (8), which is the byte's `code`. */
|
|
166
|
+
export const KEY_BACKSPACE: i32 = 11
|
|
167
|
+
|
|
168
|
+
/** Any other control byte below 32; the event's `code` is the byte (Ctrl-A is 1). */
|
|
169
|
+
export const KEY_CONTROL: i32 = 12
|
|
170
|
+
|
|
171
|
+
/** An escape sequence this decoder does not name; the event's `code` is its final byte. */
|
|
172
|
+
export const KEY_UNKNOWN: i32 = 13
|
|
173
|
+
|
|
174
|
+
/** F1: `ESC O P`, `ESC [ 1 ; m P`, `ESC [ 11 ~` or the Linux console's `ESC [ [ A`. F1–F12 are consecutive, so Fn is `KEY_F1 + n - 1`. */
|
|
175
|
+
export const KEY_F1: i32 = 14
|
|
176
|
+
|
|
177
|
+
/** F2: `ESC O Q`, `ESC [ 1 ; m Q`, `ESC [ 12 ~` or `ESC [ [ B`. */
|
|
178
|
+
export const KEY_F2: i32 = 15
|
|
179
|
+
|
|
180
|
+
/** F3: `ESC O R`, `ESC [ 1 ; m R` (when no cursor report is awaited), `ESC [ 13 ~` or `ESC [ [ C`. */
|
|
181
|
+
export const KEY_F3: i32 = 16
|
|
182
|
+
|
|
183
|
+
/** F4: `ESC O S`, `ESC [ 1 ; m S`, `ESC [ 14 ~` or `ESC [ [ D`. */
|
|
184
|
+
export const KEY_F4: i32 = 17
|
|
185
|
+
|
|
186
|
+
/** F5: `ESC [ 15 ~` or `ESC [ [ E`. */
|
|
187
|
+
export const KEY_F5: i32 = 18
|
|
188
|
+
|
|
189
|
+
/** F6: `ESC [ 17 ~`. */
|
|
190
|
+
export const KEY_F6: i32 = 19
|
|
191
|
+
|
|
192
|
+
/** F7: `ESC [ 18 ~`. */
|
|
193
|
+
export const KEY_F7: i32 = 20
|
|
194
|
+
|
|
195
|
+
/** F8: `ESC [ 19 ~`. */
|
|
196
|
+
export const KEY_F8: i32 = 21
|
|
197
|
+
|
|
198
|
+
/** F9: `ESC [ 20 ~`. */
|
|
199
|
+
export const KEY_F9: i32 = 22
|
|
200
|
+
|
|
201
|
+
/** F10: `ESC [ 21 ~`. */
|
|
202
|
+
export const KEY_F10: i32 = 23
|
|
203
|
+
|
|
204
|
+
/** F11: `ESC [ 23 ~`. */
|
|
205
|
+
export const KEY_F11: i32 = 24
|
|
206
|
+
|
|
207
|
+
/** F12: `ESC [ 24 ~`. */
|
|
208
|
+
export const KEY_F12: i32 = 25
|
|
209
|
+
|
|
210
|
+
/** Home: `ESC [ H`, `ESC O H`, `ESC [ 1 ~` or `ESC [ 7 ~`. */
|
|
211
|
+
export const KEY_HOME: i32 = 26
|
|
212
|
+
|
|
213
|
+
/** End: `ESC [ F`, `ESC O F`, `ESC [ 4 ~` or `ESC [ 8 ~`. */
|
|
214
|
+
export const KEY_END: i32 = 27
|
|
215
|
+
|
|
216
|
+
/** Page Up: `ESC [ 5 ~`. */
|
|
217
|
+
export const KEY_PAGE_UP: i32 = 28
|
|
218
|
+
|
|
219
|
+
/** Page Down: `ESC [ 6 ~`. */
|
|
220
|
+
export const KEY_PAGE_DOWN: i32 = 29
|
|
221
|
+
|
|
222
|
+
/** Insert: `ESC [ 2 ~`. */
|
|
223
|
+
export const KEY_INSERT: i32 = 30
|
|
224
|
+
|
|
225
|
+
/** Delete (the key, not DEL the byte): `ESC [ 3 ~`. */
|
|
226
|
+
export const KEY_DELETE: i32 = 31
|
|
227
|
+
|
|
228
|
+
/** A `TermEvent` that is a key: `key`, `code`, `modifiers` and `action`. */
|
|
229
|
+
export const EVENT_KEY: i32 = 1
|
|
230
|
+
|
|
231
|
+
/** A `TermEvent` that is the mouse: `button`, `action`, `modifiers`, the cell `x`, `y` and, under mode 1016, the pixel `px`, `py`. */
|
|
232
|
+
export const EVENT_MOUSE: i32 = 2
|
|
233
|
+
|
|
234
|
+
/** A `TermEvent` that is a bracketed paste: `text` is everything between `ESC [ 200 ~` and `ESC [ 201 ~`. */
|
|
235
|
+
export const EVENT_PASTE: i32 = 3
|
|
236
|
+
|
|
237
|
+
/** A `TermEvent` that is a focus change, `ESC [ I` or `ESC [ O`: `gained` says which. */
|
|
238
|
+
export const EVENT_FOCUS: i32 = 4
|
|
239
|
+
|
|
240
|
+
/** A `TermEvent` that is a new terminal size. The decoder never makes one: it is for the code that watches the size. */
|
|
241
|
+
export const EVENT_RESIZE: i32 = 5
|
|
242
|
+
|
|
243
|
+
/** A `TermEvent` that is the terminal answering a query: `introducer`, `prefix`, `params`, `intermediate`, `reply` and `text`. Never a key. */
|
|
244
|
+
export const EVENT_REPLY: i32 = 6
|
|
245
|
+
|
|
246
|
+
/** A `TermEvent` that asks the program to quit: what `KeyState` makes of Ctrl-C. */
|
|
247
|
+
export const EVENT_QUIT: i32 = 7
|
|
248
|
+
|
|
249
|
+
/** A key or button went down. */
|
|
250
|
+
export const ACTION_PRESS: i32 = 1
|
|
251
|
+
|
|
252
|
+
/** A held key repeated. */
|
|
253
|
+
export const ACTION_REPEAT: i32 = 2
|
|
254
|
+
|
|
255
|
+
/** A key or button came up. */
|
|
256
|
+
export const ACTION_RELEASE: i32 = 3
|
|
257
|
+
|
|
258
|
+
/** The mouse moved, with `button` held or `MOUSE_NONE`. */
|
|
259
|
+
export const ACTION_MOVE: i32 = 4
|
|
260
|
+
|
|
261
|
+
/** The Shift bit of `modifiers`. The bits are the kitty keyboard protocol's modifier value minus one, which the xterm `; m` forms share. */
|
|
262
|
+
export const MOD_SHIFT: i32 = 1
|
|
263
|
+
|
|
264
|
+
/** The Alt bit of `modifiers`; also set for an ESC followed by an ASCII key. */
|
|
265
|
+
export const MOD_ALT: i32 = 2
|
|
266
|
+
|
|
267
|
+
/** The Ctrl bit of `modifiers`. */
|
|
268
|
+
export const MOD_CTRL: i32 = 4
|
|
269
|
+
|
|
270
|
+
/** The Super bit of `modifiers` (the Windows or Command key). */
|
|
271
|
+
export const MOD_SUPER: i32 = 8
|
|
272
|
+
|
|
273
|
+
/** The Hyper bit of `modifiers` (kitty only). */
|
|
274
|
+
export const MOD_HYPER: i32 = 16
|
|
275
|
+
|
|
276
|
+
/** The Meta bit of `modifiers` (kitty only). */
|
|
277
|
+
export const MOD_META: i32 = 32
|
|
278
|
+
|
|
279
|
+
/** No button: a move with nothing held, or a release a terminal does not name. */
|
|
280
|
+
export const MOUSE_NONE: i32 = 0
|
|
281
|
+
|
|
282
|
+
/** The left button. */
|
|
283
|
+
export const MOUSE_LEFT: i32 = 1
|
|
284
|
+
|
|
285
|
+
/** The middle button. */
|
|
286
|
+
export const MOUSE_MIDDLE: i32 = 2
|
|
287
|
+
|
|
288
|
+
/** The right button. */
|
|
289
|
+
export const MOUSE_RIGHT: i32 = 3
|
|
290
|
+
|
|
291
|
+
/** The wheel turned up; `MOUSE_WHEEL_DOWN`, `_LEFT` and `_RIGHT` follow it. A wheel only ever presses. */
|
|
292
|
+
export const MOUSE_WHEEL_UP: i32 = 4
|
|
293
|
+
|
|
294
|
+
/** The wheel turned down. */
|
|
295
|
+
export const MOUSE_WHEEL_DOWN: i32 = 5
|
|
296
|
+
|
|
297
|
+
/** The wheel tilted left. */
|
|
298
|
+
export const MOUSE_WHEEL_LEFT: i32 = 6
|
|
299
|
+
|
|
300
|
+
/** The wheel tilted right. */
|
|
301
|
+
export const MOUSE_WHEEL_RIGHT: i32 = 7
|
|
302
|
+
|
|
303
|
+
/** Button 8 (back on most mice); 9, 10 and 11 follow it. */
|
|
304
|
+
export const MOUSE_BUTTON_8: i32 = 8
|
|
305
|
+
|
|
306
|
+
/** The escape byte that starts every control sequence. */
|
|
307
|
+
const TERM_ESC: i32 = 27
|
|
308
|
+
|
|
309
|
+
/** The space a blank cell holds. */
|
|
310
|
+
const TERM_SPACE: i32 = 32
|
|
311
|
+
|
|
312
|
+
/** DEL, the first byte past printable ASCII. */
|
|
313
|
+
const TERM_DELETE: i32 = 127
|
|
314
|
+
|
|
315
|
+
/** `[`, which follows ESC to begin a control sequence. */
|
|
316
|
+
const TERM_BRACKET: i32 = 91
|
|
317
|
+
|
|
318
|
+
/** `;`, between a control sequence's parameters. */
|
|
319
|
+
const TERM_SEMICOLON: i32 = 59
|
|
320
|
+
|
|
321
|
+
/** `0`, the first decimal digit. */
|
|
322
|
+
const TERM_DIGIT_0: i32 = 48
|
|
323
|
+
|
|
324
|
+
/** `H`, the final byte of a cursor position: `ESC [ row ; col H`. */
|
|
325
|
+
const TERM_FINAL_POSITION: i32 = 72
|
|
326
|
+
|
|
327
|
+
/** `C`, the final byte of a cursor move along the row: `ESC [ n C`. */
|
|
328
|
+
const TERM_FINAL_FORWARD: i32 = 67
|
|
329
|
+
|
|
330
|
+
/** `m`, the final byte of a style (SGR). */
|
|
331
|
+
const TERM_FINAL_STYLE: i32 = 109
|
|
332
|
+
|
|
333
|
+
/** `?`, which marks a DEC private mode: `ESC [ ? n h` sets mode n, `ESC [ ? n l` resets it. */
|
|
334
|
+
const TERM_PRIVATE: i32 = 63
|
|
335
|
+
|
|
336
|
+
/** `h`, the final byte that sets a mode. */
|
|
337
|
+
const TERM_FINAL_SET: i32 = 104
|
|
338
|
+
|
|
339
|
+
/** `l`, the final byte that resets a mode. */
|
|
340
|
+
const TERM_FINAL_RESET: i32 = 108
|
|
341
|
+
|
|
342
|
+
/** The DEC private mode of synchronized output: the terminal holds what it is sent until the mode is reset, then paints it at once. */
|
|
343
|
+
const TERM_SYNC_MODE: i32 = 2026
|
|
344
|
+
|
|
345
|
+
/** The SGR base of a foreground colour: 38 sets one, 39 is the default. */
|
|
346
|
+
const TERM_SGR_FOREGROUND: i32 = 30
|
|
347
|
+
|
|
348
|
+
/** The SGR base of a background colour: 48 sets one, 49 is the default. */
|
|
349
|
+
const TERM_SGR_BACKGROUND: i32 = 40
|
|
350
|
+
|
|
351
|
+
/** A front-buffer code point no glyph has, so the cell is sent on the next flush. */
|
|
352
|
+
const TERM_UNSENT: i32 = -1
|
|
353
|
+
|
|
354
|
+
/** A style the terminal is not known to be in, so the next cell sends its own in full. */
|
|
355
|
+
const TERM_STYLE_UNKNOWN: i32 = -2
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* The most bytes one cell can add to a flush: a move, a full SGR and a 4-byte
|
|
359
|
+
* glyph, rounded up. A flush reserves this once more for what is not a cell:
|
|
360
|
+
* the closing `ESC [ 0 m` and the two synchronized-output escapes, 20 bytes.
|
|
361
|
+
*/
|
|
362
|
+
const TERM_CELL_BYTES: i32 = 64
|
|
363
|
+
|
|
364
|
+
/** The most cells a side of a `Screen` has; a terminal reports up to 65,535. */
|
|
365
|
+
const TERM_MAX_SIDE: i32 = 4096
|
|
366
|
+
|
|
367
|
+
/** The most cells a `Screen` has, so its `flush` buffer stays at 64 MiB. */
|
|
368
|
+
const TERM_MAX_CELLS: i32 = 1048576
|
|
369
|
+
|
|
370
|
+
/** The most parameter bytes an `ESC [` holds before it is given up as `KEY_ESCAPE`. */
|
|
371
|
+
const TERM_MAX_CSI_PARAMS: i32 = 32
|
|
372
|
+
|
|
373
|
+
/** The most unchanged cells `flush` rewrites to bridge a gap rather than move the cursor over it. */
|
|
374
|
+
const TERM_BRIDGE: i32 = 3
|
|
375
|
+
|
|
376
|
+
/** The upper half block, U+2580: a `Canvas` cell's top pixel in the foreground. */
|
|
377
|
+
const TERM_UPPER_HALF: i32 = 9600
|
|
378
|
+
|
|
379
|
+
/** The lower half block, U+2584: a `Canvas` cell with only its bottom pixel set. */
|
|
380
|
+
const TERM_LOWER_HALF: i32 = 9604
|
|
381
|
+
|
|
382
|
+
/** U+FFFD, decoded from a byte that does not begin or continue a UTF-8 sequence. */
|
|
383
|
+
const TERM_REPLACEMENT: i32 = 65533
|
|
384
|
+
|
|
385
|
+
/** Whether a string here is UTF-8 bytes (natively) rather than UTF-16 units (under Node). */
|
|
386
|
+
const termNativeStrings = (): boolean => toI32("▀".length) === 3
|
|
387
|
+
|
|
388
|
+
/** `v` held to `lo`–`hi`. */
|
|
389
|
+
const termClamp = (v: i32, lo: i32, hi: i32): i32 => {
|
|
390
|
+
if (v < lo) {
|
|
391
|
+
return lo
|
|
392
|
+
}
|
|
393
|
+
return v > hi ? hi : v
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/** How many bytes a UTF-8 character whose first byte is `c` (128 or more) says it takes. */
|
|
397
|
+
const termLeadWidth = (c: i32): i32 => {
|
|
398
|
+
if (c >= 240) {
|
|
399
|
+
return 4
|
|
400
|
+
}
|
|
401
|
+
if (c >= 224) {
|
|
402
|
+
return 3
|
|
403
|
+
}
|
|
404
|
+
return c >= 192 ? 2 : 1
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/** The code point bits the first byte `c` of a `width`-byte UTF-8 character carries. */
|
|
408
|
+
const termLeadBits = (c: i32, width: i32): i32 => {
|
|
409
|
+
if (width === 2) {
|
|
410
|
+
return c & 31
|
|
411
|
+
}
|
|
412
|
+
return width === 3 ? c & 15 : c & 7
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/** The colour with red, green and blue each in 0–255, as `0xRRGGBB`; a component outside is clamped. */
|
|
416
|
+
export const rgb = (r: i32, g: i32, b: i32): i32 =>
|
|
417
|
+
(termClamp(r, 0, 255) << 16) | (termClamp(g, 0, 255) << 8) | termClamp(b, 0, 255)
|
|
418
|
+
|
|
419
|
+
/** The index of the 6×6×6 colour cube's level (0–5) nearest one component in 0–255. */
|
|
420
|
+
const termCubeLevel = (v: i32): i32 => {
|
|
421
|
+
if (v < 48) {
|
|
422
|
+
return 0
|
|
423
|
+
}
|
|
424
|
+
if (v < 115) {
|
|
425
|
+
return 1
|
|
426
|
+
}
|
|
427
|
+
return ((v - 35) / 40) | 0
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/** The component value of cube level `q`: 0, 95, 135, 175, 215 or 255. */
|
|
431
|
+
const termCubeValue = (q: i32): i32 => (q === 0 ? 0 : q * 40 + 55)
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* The 256-colour palette index nearest `colour`: one of the 6×6×6 cube
|
|
435
|
+
* (16–231) or of the 24 greys (232–255), whichever is closer in RGB.
|
|
436
|
+
* `COLOR_DEFAULT` stays itself.
|
|
437
|
+
*/
|
|
438
|
+
export const paletteIndex = (colour: i32): i32 => {
|
|
439
|
+
if (colour < 0) {
|
|
440
|
+
return COLOR_DEFAULT
|
|
441
|
+
}
|
|
442
|
+
const r: i32 = (colour >> 16) & 255
|
|
443
|
+
const g: i32 = (colour >> 8) & 255
|
|
444
|
+
const b: i32 = colour & 255
|
|
445
|
+
const qr: i32 = termCubeLevel(r)
|
|
446
|
+
const qg: i32 = termCubeLevel(g)
|
|
447
|
+
const qb: i32 = termCubeLevel(b)
|
|
448
|
+
const cube: i32 = qr * 36 + qg * 6 + qb + 16
|
|
449
|
+
const dr: i32 = r - termCubeValue(qr)
|
|
450
|
+
const dg: i32 = g - termCubeValue(qg)
|
|
451
|
+
const db: i32 = b - termCubeValue(qb)
|
|
452
|
+
const cubeDistance: i32 = dr * dr + dg * dg + db * db
|
|
453
|
+
const average: i32 = ((r + g + b) / 3) | 0
|
|
454
|
+
const step: i32 = average > 238 ? 23 : termClamp(((average - 3) / 10) | 0, 0, 23)
|
|
455
|
+
const grey: i32 = step * 10 + 8
|
|
456
|
+
const gr: i32 = r - grey
|
|
457
|
+
const gg: i32 = g - grey
|
|
458
|
+
const gb: i32 = b - grey
|
|
459
|
+
const greyDistance: i32 = gr * gr + gg * gg + gb * gb
|
|
460
|
+
return greyDistance < cubeDistance ? step + 232 : cube
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/** Switch to the alternate screen, which the terminal restores the old contents from on leaving. */
|
|
464
|
+
export const enterAltScreen = (): string => "\x1b[?1049h"
|
|
465
|
+
|
|
466
|
+
/** Leave the alternate screen and show what was there before `enterAltScreen`. */
|
|
467
|
+
export const leaveAltScreen = (): string => "\x1b[?1049l"
|
|
468
|
+
|
|
469
|
+
/** Hide the cursor, so a redraw does not flicker it across the screen. */
|
|
470
|
+
export const hideCursor = (): string => "\x1b[?25l"
|
|
471
|
+
|
|
472
|
+
/** Show the cursor again. */
|
|
473
|
+
export const showCursor = (): string => "\x1b[?25h"
|
|
474
|
+
|
|
475
|
+
/** Move the cursor home and erase the whole screen; a `Screen` that was drawn on wants `invalidate` after it. */
|
|
476
|
+
export const clearScreen = (): string => "\x1b[H\x1b[2J"
|
|
477
|
+
|
|
478
|
+
/** Reset colour and attributes to the terminal's defaults: SGR 0. */
|
|
479
|
+
export const resetStyle = (): string => "\x1b[0m"
|
|
480
|
+
|
|
481
|
+
/** Move the cursor to column `x`, row `y`, both from 0. */
|
|
482
|
+
export const moveCursor = (x: i32, y: i32): string => `\x1b[${y + 1};${x + 1}H`
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Begin a synchronized update (DEC mode 2026): a terminal that knows the mode
|
|
486
|
+
* holds what follows until `endSync`, then paints it in one go, so a frame is
|
|
487
|
+
* never seen half drawn. One that does not ignores it.
|
|
488
|
+
*/
|
|
489
|
+
export const beginSync = (): string => "\x1b[?2026h"
|
|
490
|
+
|
|
491
|
+
/** End a synchronized update, so the terminal paints what `beginSync` held. */
|
|
492
|
+
export const endSync = (): string => "\x1b[?2026l"
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* The code point of the character that starts at unit `i` of `s`: UTF-8 bytes
|
|
496
|
+
* natively, UTF-16 units under Node. A unit that cannot start one is U+FFFD.
|
|
497
|
+
*/
|
|
498
|
+
const termCodePointAt = (s: string, i: i32, n: i32): i32 => {
|
|
499
|
+
const c: i32 = toI32(s.charCodeAt(i))
|
|
500
|
+
if (!termNativeStrings()) {
|
|
501
|
+
if (c >= 55296 && c < 56320 && i + 1 < n) {
|
|
502
|
+
const lo: i32 = toI32(s.charCodeAt(i + 1))
|
|
503
|
+
if (lo >= 56320 && lo < 57344) {
|
|
504
|
+
return ((c - 55296) << 10) + (lo - 56320) + 65536
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
return c
|
|
508
|
+
}
|
|
509
|
+
const width: i32 = termCodePointUnits(s, i, n)
|
|
510
|
+
if (c < 128) {
|
|
511
|
+
return c
|
|
512
|
+
}
|
|
513
|
+
if (width === 1) {
|
|
514
|
+
return TERM_REPLACEMENT
|
|
515
|
+
}
|
|
516
|
+
let cp: i32 = termLeadBits(c, width)
|
|
517
|
+
for (let k: i32 = 1; k < width; k++) {
|
|
518
|
+
cp = (cp << 6) | (toI32(s.charCodeAt(i + k)) & 63)
|
|
519
|
+
}
|
|
520
|
+
return cp
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/** How many units of `s` the character at `i` takes: 1–4 bytes natively, 1 or 2 units under Node. */
|
|
524
|
+
const termCodePointUnits = (s: string, i: i32, n: i32): i32 => {
|
|
525
|
+
const c: i32 = toI32(s.charCodeAt(i))
|
|
526
|
+
if (!termNativeStrings()) {
|
|
527
|
+
if (c >= 55296 && c < 56320 && i + 1 < n) {
|
|
528
|
+
const lo: i32 = toI32(s.charCodeAt(i + 1))
|
|
529
|
+
return lo >= 56320 && lo < 57344 ? 2 : 1
|
|
530
|
+
}
|
|
531
|
+
return 1
|
|
532
|
+
}
|
|
533
|
+
const width: i32 = c >= 248 ? 1 : termLeadWidth(c)
|
|
534
|
+
if (width === 1 || i + width > n) {
|
|
535
|
+
return 1
|
|
536
|
+
}
|
|
537
|
+
for (let k: i32 = 1; k < width; k++) {
|
|
538
|
+
const next: i32 = toI32(s.charCodeAt(i + k))
|
|
539
|
+
if (next < 128 || next >= 192) {
|
|
540
|
+
return 1
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
return width
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* The string of `bytes[0, n)`, which hold UTF-8. Natively that is the bytes
|
|
548
|
+
* themselves; under Node each code point becomes its UTF-16 units, so `write`
|
|
549
|
+
* sends the same bytes back out.
|
|
550
|
+
*/
|
|
551
|
+
const termBytesText = (bytes: u8[], n: i32): string => {
|
|
552
|
+
if (termNativeStrings()) {
|
|
553
|
+
return String.fromBytes(bytes, 0, n)
|
|
554
|
+
}
|
|
555
|
+
const pieces: string[] = []
|
|
556
|
+
let run: i32 = 0
|
|
557
|
+
let i: i32 = 0
|
|
558
|
+
while (i < n) {
|
|
559
|
+
if (!(i >= 0 && i < toI32(bytes.length))) {
|
|
560
|
+
panic("nish/term: a byte count past the end of its buffer")
|
|
561
|
+
}
|
|
562
|
+
const c: i32 = toI32(bytes[i])
|
|
563
|
+
if (c < 128) {
|
|
564
|
+
i = i + 1
|
|
565
|
+
continue
|
|
566
|
+
}
|
|
567
|
+
if (run < i) {
|
|
568
|
+
pieces.push(String.fromBytes(bytes, run, i))
|
|
569
|
+
}
|
|
570
|
+
const width: i32 = termLeadWidth(c)
|
|
571
|
+
let cp: i32 = width === 1 ? TERM_REPLACEMENT : termLeadBits(c, width)
|
|
572
|
+
for (let k: i32 = 1; k < width && i + k < n; k++) {
|
|
573
|
+
cp = (cp << 6) | (toI32(bytes[i + k]) & 63)
|
|
574
|
+
}
|
|
575
|
+
if (cp >= 65536) {
|
|
576
|
+
const v: i32 = cp - 65536
|
|
577
|
+
pieces.push(String.fromCharCode((v >> 10) + 55296) + String.fromCharCode((v & 1023) + 56320))
|
|
578
|
+
} else {
|
|
579
|
+
pieces.push(String.fromCharCode(cp))
|
|
580
|
+
}
|
|
581
|
+
i = i + width
|
|
582
|
+
run = i
|
|
583
|
+
}
|
|
584
|
+
if (run < n) {
|
|
585
|
+
pieces.push(String.fromBytes(bytes, run, n))
|
|
586
|
+
}
|
|
587
|
+
return pieces.join("")
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/** A grid of coloured cells, double-buffered, and the escapes that bring a terminal up to date with it. */
|
|
591
|
+
export class Screen {
|
|
592
|
+
/** The frame being drawn, one code point per cell, row by row. */
|
|
593
|
+
glyphs: i32[]
|
|
594
|
+
/** The frame's foregrounds, laid out as `glyphs`: a colour or `COLOR_DEFAULT`. */
|
|
595
|
+
fgs: i32[]
|
|
596
|
+
/** The frame's backgrounds, laid out as `glyphs`. */
|
|
597
|
+
bgs: i32[]
|
|
598
|
+
/** The frame's attributes, laid out as `glyphs`: `ATTR_BOLD` and `ATTR_INVERSE`, or'd. */
|
|
599
|
+
attrs: i32[]
|
|
600
|
+
/** The code points the terminal was last sent; -1 is a cell not yet sent. */
|
|
601
|
+
sentGlyphs: i32[]
|
|
602
|
+
/** The foregrounds the terminal was last sent. */
|
|
603
|
+
sentFgs: i32[]
|
|
604
|
+
/** The backgrounds the terminal was last sent. */
|
|
605
|
+
sentBgs: i32[]
|
|
606
|
+
/** The attributes the terminal was last sent. */
|
|
607
|
+
sentAttrs: i32[]
|
|
608
|
+
/** The bytes `flush` builds, sized by `resize` and reused from frame to frame. */
|
|
609
|
+
out: u8[]
|
|
610
|
+
/** Columns. */
|
|
611
|
+
width: i32
|
|
612
|
+
/** Rows. */
|
|
613
|
+
height: i32
|
|
614
|
+
/** How many of `out` the flush in progress has written. */
|
|
615
|
+
outLen: i32
|
|
616
|
+
/** Whether colours go out as 256-colour palette indices rather than 24-bit RGB. */
|
|
617
|
+
palette256: boolean
|
|
618
|
+
/** Whether a `flush` that changes anything is wrapped in `beginSync` and `endSync`; off until set. */
|
|
619
|
+
sync: boolean
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* A `width × height` screen of blank cells in the terminal's colours, none
|
|
623
|
+
* of them sent yet, so the first `flush` draws them all. With `palette256`
|
|
624
|
+
* set, every colour is sent as its nearest 256-colour index (`paletteIndex`).
|
|
625
|
+
*/
|
|
626
|
+
constructor(width: i32, height: i32, palette256: boolean) {
|
|
627
|
+
this.width = width < 0 ? 0 : width
|
|
628
|
+
this.height = height < 0 ? 0 : height
|
|
629
|
+
this.palette256 = palette256
|
|
630
|
+
this.sync = false
|
|
631
|
+
this.glyphs = []
|
|
632
|
+
this.fgs = []
|
|
633
|
+
this.bgs = []
|
|
634
|
+
this.attrs = []
|
|
635
|
+
this.sentGlyphs = []
|
|
636
|
+
this.sentFgs = []
|
|
637
|
+
this.sentBgs = []
|
|
638
|
+
this.sentAttrs = []
|
|
639
|
+
this.out = []
|
|
640
|
+
this.outLen = 0
|
|
641
|
+
this.resize(this.width, this.height)
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* Make the screen `width × height`, blank and unsent: a terminal that changed
|
|
646
|
+
* size is redrawn in full. Each side is at most 4,096 cells and the screen at
|
|
647
|
+
* most 1,048,576, rows dropped to fit, so no size a terminal reports makes it
|
|
648
|
+
* overflow or run out of memory.
|
|
649
|
+
*/
|
|
650
|
+
resize(width: i32, height: i32): void {
|
|
651
|
+
this.width = termClamp(width, 0, TERM_MAX_SIDE)
|
|
652
|
+
this.height = termClamp(height, 0, TERM_MAX_SIDE)
|
|
653
|
+
if (this.width * this.height > TERM_MAX_CELLS) {
|
|
654
|
+
this.height = (TERM_MAX_CELLS / this.width) | 0
|
|
655
|
+
}
|
|
656
|
+
const cells: i32 = this.width * this.height
|
|
657
|
+
this.glyphs = new Array<i32>(cells)
|
|
658
|
+
this.fgs = new Array<i32>(cells)
|
|
659
|
+
this.bgs = new Array<i32>(cells)
|
|
660
|
+
this.attrs = new Array<i32>(cells)
|
|
661
|
+
this.sentGlyphs = new Array<i32>(cells)
|
|
662
|
+
this.sentFgs = new Array<i32>(cells)
|
|
663
|
+
this.sentBgs = new Array<i32>(cells)
|
|
664
|
+
this.sentAttrs = new Array<i32>(cells)
|
|
665
|
+
// Sized here rather than in `flush`, so a flush stores nothing into the
|
|
666
|
+
// screen and a frame loop around it can take its string back every pass.
|
|
667
|
+
this.out = new Array<u8>(cells * TERM_CELL_BYTES + TERM_CELL_BYTES)
|
|
668
|
+
this.clear(COLOR_DEFAULT)
|
|
669
|
+
this.invalidate()
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
/** Blank every cell of the frame being drawn: a space on `background`, default foreground, no attributes. */
|
|
673
|
+
clear(background: i32): void {
|
|
674
|
+
this.glyphs.fill(TERM_SPACE)
|
|
675
|
+
this.fgs.fill(COLOR_DEFAULT)
|
|
676
|
+
this.bgs.fill(background)
|
|
677
|
+
this.attrs.fill(ATTR_NONE)
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/** Forget what the terminal shows, so the next `flush` sends every cell: after a `clearScreen`, say. */
|
|
681
|
+
invalidate(): void {
|
|
682
|
+
this.sentGlyphs.fill(TERM_UNSENT)
|
|
683
|
+
this.sentFgs.fill(COLOR_DEFAULT)
|
|
684
|
+
this.sentBgs.fill(COLOR_DEFAULT)
|
|
685
|
+
this.sentAttrs.fill(ATTR_NONE)
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* Draw code point `glyph` at column `x`, row `y`; a cell off the screen is
|
|
690
|
+
* not drawn. Bits of `attrs` other than `ATTR_BOLD` and `ATTR_INVERSE` are
|
|
691
|
+
* dropped, so `flush` never has an attribute it cannot write.
|
|
692
|
+
*/
|
|
693
|
+
set(x: i32, y: i32, glyph: i32, fg: i32, bg: i32, attrs: i32): void {
|
|
694
|
+
if (x < 0 || y < 0 || x >= this.width || y >= this.height) {
|
|
695
|
+
return
|
|
696
|
+
}
|
|
697
|
+
const i: i32 = y * this.width + x
|
|
698
|
+
if (!(i >= 0 && i < toI32(this.glyphs.length))) {
|
|
699
|
+
return
|
|
700
|
+
}
|
|
701
|
+
this.glyphs[i] = glyph
|
|
702
|
+
this.fgs[i] = fg
|
|
703
|
+
this.bgs[i] = bg
|
|
704
|
+
this.attrs[i] = attrs & (ATTR_BOLD | ATTR_INVERSE)
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
/**
|
|
708
|
+
* Draw `s` from column `x` of row `y`, one cell per character, clipped at
|
|
709
|
+
* the edges, and answer the column after its last character.
|
|
710
|
+
*/
|
|
711
|
+
text(x: i32, y: i32, s: string, fg: i32, bg: i32, attrs: i32): i32 {
|
|
712
|
+
const n: i32 = toI32(s.length)
|
|
713
|
+
let column: i32 = x
|
|
714
|
+
let i: i32 = 0
|
|
715
|
+
while (i < n) {
|
|
716
|
+
this.set(column, y, termCodePointAt(s, i, n), fg, bg, attrs)
|
|
717
|
+
i = i + termCodePointUnits(s, i, n)
|
|
718
|
+
column = column + 1
|
|
719
|
+
}
|
|
720
|
+
return column
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
/** The code point drawn at column `x`, row `y` of the frame being drawn, or -1 off the screen. */
|
|
724
|
+
glyphAt(x: i32, y: i32): i32 {
|
|
725
|
+
if (x < 0 || y < 0 || x >= this.width || y >= this.height) {
|
|
726
|
+
return -1
|
|
727
|
+
}
|
|
728
|
+
return this.glyphs[y * this.width + x]
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
/** Append one byte to the flush in progress; `flush` has reserved the room. */
|
|
732
|
+
put(b: i32): void {
|
|
733
|
+
this.out[this.outLen] = toU8(b)
|
|
734
|
+
this.outLen = this.outLen + 1
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
/** Append `v`, at least 0, in decimal: the three digits a colour channel has without recursing. */
|
|
738
|
+
putDecimal(v: i32): void {
|
|
739
|
+
if (v >= 100) {
|
|
740
|
+
if (v >= 1000) {
|
|
741
|
+
this.putDecimal((v / 1000) | 0)
|
|
742
|
+
}
|
|
743
|
+
this.put((((v / 100) | 0) % 10) + TERM_DIGIT_0)
|
|
744
|
+
}
|
|
745
|
+
if (v >= 10) {
|
|
746
|
+
this.put((((v / 10) | 0) % 10) + TERM_DIGIT_0)
|
|
747
|
+
}
|
|
748
|
+
this.put((v % 10) + TERM_DIGIT_0)
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/** Append `ESC [ ? 2026` and `final`, which sets or resets synchronized output. */
|
|
752
|
+
putSyncMode(final: i32): void {
|
|
753
|
+
this.put(TERM_ESC)
|
|
754
|
+
this.put(TERM_BRACKET)
|
|
755
|
+
this.put(TERM_PRIVATE)
|
|
756
|
+
this.putDecimal(TERM_SYNC_MODE)
|
|
757
|
+
this.put(final)
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
/**
|
|
761
|
+
* Append code point `cp` as UTF-8. One that would not draw a character in
|
|
762
|
+
* place, or is not a scalar value — a C0 or C1 control, DEL, a surrogate, a
|
|
763
|
+
* negative number — goes as U+FFFD, so a cell cannot move the cursor, open
|
|
764
|
+
* an escape or send bytes that are not UTF-8.
|
|
765
|
+
*/
|
|
766
|
+
putGlyph(cp: i32): void {
|
|
767
|
+
if (cp >= TERM_SPACE && cp < TERM_DELETE) {
|
|
768
|
+
this.put(cp)
|
|
769
|
+
} else if (cp >= 160 && cp < 2048) {
|
|
770
|
+
this.put((cp >> 6) | 192)
|
|
771
|
+
this.put((cp & 63) | 128)
|
|
772
|
+
} else if (cp >= 65536 && cp < 1114112) {
|
|
773
|
+
this.put((cp >> 18) | 240)
|
|
774
|
+
this.put(((cp >> 12) & 63) | 128)
|
|
775
|
+
this.put(((cp >> 6) & 63) | 128)
|
|
776
|
+
this.put((cp & 63) | 128)
|
|
777
|
+
} else {
|
|
778
|
+
const c: i32 = cp >= 2048 && cp < 65536 && !(cp >= 55296 && cp < 57344) ? cp : TERM_REPLACEMENT
|
|
779
|
+
this.put((c >> 12) | 224)
|
|
780
|
+
this.put(((c >> 6) & 63) | 128)
|
|
781
|
+
this.put((c & 63) | 128)
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
/** Append the SGR parameters for colour `key` (an RGB or a palette index) on base 30 (foreground) or 40. */
|
|
786
|
+
putColour(base: i32, key: i32): void {
|
|
787
|
+
if (key < 0) {
|
|
788
|
+
this.putDecimal(base + 9)
|
|
789
|
+
return
|
|
790
|
+
}
|
|
791
|
+
this.putDecimal(base + 8)
|
|
792
|
+
if (this.palette256) {
|
|
793
|
+
this.put(TERM_SEMICOLON)
|
|
794
|
+
this.put(TERM_DIGIT_0 + 5)
|
|
795
|
+
this.put(TERM_SEMICOLON)
|
|
796
|
+
this.putDecimal(key)
|
|
797
|
+
return
|
|
798
|
+
}
|
|
799
|
+
this.put(TERM_SEMICOLON)
|
|
800
|
+
this.put(TERM_DIGIT_0 + 2)
|
|
801
|
+
this.put(TERM_SEMICOLON)
|
|
802
|
+
this.putDecimal((key >> 16) & 255)
|
|
803
|
+
this.put(TERM_SEMICOLON)
|
|
804
|
+
this.putDecimal((key >> 8) & 255)
|
|
805
|
+
this.put(TERM_SEMICOLON)
|
|
806
|
+
this.putDecimal(key & 255)
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
/** The value a colour is sent as and compared by: itself, or its palette index under `palette256`. */
|
|
810
|
+
colourKey(colour: i32): i32 {
|
|
811
|
+
if (colour < 0) {
|
|
812
|
+
return COLOR_DEFAULT
|
|
813
|
+
}
|
|
814
|
+
return this.palette256 ? paletteIndex(colour) : colour
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
/**
|
|
818
|
+
* The escapes and glyphs that turn the frame last sent into the frame drawn,
|
|
819
|
+
* and the drawn frame becomes the sent one. Write the answer with `write`;
|
|
820
|
+
* an empty string means nothing changed.
|
|
821
|
+
*
|
|
822
|
+
* Changed cells are visited row by row. The cursor is moved only when it is
|
|
823
|
+
* not already at the cell — `ESC [ n C` along the row, `ESC [ row ; col H`
|
|
824
|
+
* otherwise — and a gap of up to three unchanged ASCII cells in the current
|
|
825
|
+
* style is rewritten instead, which is shorter than the move. A style is
|
|
826
|
+
* sent only when it differs from the previous cell's, and only the parts
|
|
827
|
+
* that differ, from a reset when an attribute has to be turned off. A flush
|
|
828
|
+
* that leaves the terminal in a style other than the default ends with
|
|
829
|
+
* `ESC [ 0 m`, so what the caller writes next starts plain. With `sync` set,
|
|
830
|
+
* a flush that changes anything is wrapped in `beginSync` and `endSync`, in
|
|
831
|
+
* the same string, so the terminal paints the frame whole.
|
|
832
|
+
*/
|
|
833
|
+
flush(): string {
|
|
834
|
+
const w: i32 = this.width
|
|
835
|
+
const cells: i32 = toI32(this.glyphs.length)
|
|
836
|
+
this.outLen = 0
|
|
837
|
+
const glyphs: i32[] = this.glyphs
|
|
838
|
+
const fgs: i32[] = this.fgs
|
|
839
|
+
const bgs: i32[] = this.bgs
|
|
840
|
+
const attrs: i32[] = this.attrs
|
|
841
|
+
const sentGlyphs: i32[] = this.sentGlyphs
|
|
842
|
+
const sentFgs: i32[] = this.sentFgs
|
|
843
|
+
const sentBgs: i32[] = this.sentBgs
|
|
844
|
+
const sentAttrs: i32[] = this.sentAttrs
|
|
845
|
+
if (
|
|
846
|
+
!(
|
|
847
|
+
toI32(fgs.length) === cells &&
|
|
848
|
+
toI32(bgs.length) === cells &&
|
|
849
|
+
toI32(attrs.length) === cells &&
|
|
850
|
+
toI32(sentGlyphs.length) === cells &&
|
|
851
|
+
toI32(sentFgs.length) === cells &&
|
|
852
|
+
toI32(sentBgs.length) === cells &&
|
|
853
|
+
toI32(sentAttrs.length) === cells &&
|
|
854
|
+
toI32(this.out.length) >= cells * TERM_CELL_BYTES + TERM_CELL_BYTES
|
|
855
|
+
)
|
|
856
|
+
) {
|
|
857
|
+
panic("nish/term: a Screen's buffers disagree on its size")
|
|
858
|
+
}
|
|
859
|
+
if (this.sync) {
|
|
860
|
+
this.putSyncMode(TERM_FINAL_SET)
|
|
861
|
+
}
|
|
862
|
+
const opened: i32 = this.outLen
|
|
863
|
+
let styleFg: i32 = TERM_STYLE_UNKNOWN
|
|
864
|
+
let styleBg: i32 = TERM_STYLE_UNKNOWN
|
|
865
|
+
let styleAttrs: i32 = TERM_STYLE_UNKNOWN
|
|
866
|
+
let cursorRow: i32 = -1
|
|
867
|
+
let cursorCol: i32 = -1
|
|
868
|
+
for (
|
|
869
|
+
let i: i32 = 0;
|
|
870
|
+
i < cells &&
|
|
871
|
+
i < toI32(glyphs.length) &&
|
|
872
|
+
i < toI32(fgs.length) &&
|
|
873
|
+
i < toI32(bgs.length) &&
|
|
874
|
+
i < toI32(attrs.length) &&
|
|
875
|
+
i < toI32(sentGlyphs.length) &&
|
|
876
|
+
i < toI32(sentFgs.length) &&
|
|
877
|
+
i < toI32(sentBgs.length) &&
|
|
878
|
+
i < toI32(sentAttrs.length);
|
|
879
|
+
i++
|
|
880
|
+
) {
|
|
881
|
+
const glyph: i32 = glyphs[i]
|
|
882
|
+
const fg: i32 = fgs[i]
|
|
883
|
+
const bg: i32 = bgs[i]
|
|
884
|
+
const at: i32 = attrs[i]
|
|
885
|
+
if (glyph === sentGlyphs[i] && fg === sentFgs[i] && bg === sentBgs[i] && at === sentAttrs[i]) {
|
|
886
|
+
continue
|
|
887
|
+
}
|
|
888
|
+
const row: i32 = (i / w) | 0
|
|
889
|
+
const col: i32 = i - row * w
|
|
890
|
+
const fgKey: i32 = this.colourKey(fg)
|
|
891
|
+
const bgKey: i32 = this.colourKey(bg)
|
|
892
|
+
if (cursorRow !== row || cursorCol !== col) {
|
|
893
|
+
const gap: i32 = col - cursorCol
|
|
894
|
+
let bridged: boolean = false
|
|
895
|
+
if (cursorRow === row && gap > 0 && gap <= TERM_BRIDGE) {
|
|
896
|
+
bridged = true
|
|
897
|
+
for (let k: i32 = i - gap; k < i; k++) {
|
|
898
|
+
if (
|
|
899
|
+
!(k >= 0) ||
|
|
900
|
+
glyphs[k] < TERM_SPACE ||
|
|
901
|
+
glyphs[k] >= TERM_DELETE ||
|
|
902
|
+
attrs[k] !== styleAttrs ||
|
|
903
|
+
this.colourKey(fgs[k]) !== styleFg ||
|
|
904
|
+
this.colourKey(bgs[k]) !== styleBg
|
|
905
|
+
) {
|
|
906
|
+
bridged = false
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
if (bridged) {
|
|
910
|
+
for (let k: i32 = i - gap; k < i; k++) {
|
|
911
|
+
if (k >= 0) {
|
|
912
|
+
this.put(glyphs[k])
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
if (!bridged) {
|
|
918
|
+
this.put(TERM_ESC)
|
|
919
|
+
this.put(TERM_BRACKET)
|
|
920
|
+
if (cursorRow === row && gap > 0) {
|
|
921
|
+
if (gap > 1) {
|
|
922
|
+
this.putDecimal(gap)
|
|
923
|
+
}
|
|
924
|
+
this.put(TERM_FINAL_FORWARD)
|
|
925
|
+
} else if (row === 0 && col === 0) {
|
|
926
|
+
this.put(TERM_FINAL_POSITION)
|
|
927
|
+
} else {
|
|
928
|
+
this.putDecimal(row + 1)
|
|
929
|
+
this.put(TERM_SEMICOLON)
|
|
930
|
+
this.putDecimal(col + 1)
|
|
931
|
+
this.put(TERM_FINAL_POSITION)
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
if (fgKey !== styleFg || bgKey !== styleBg || at !== styleAttrs) {
|
|
936
|
+
this.put(TERM_ESC)
|
|
937
|
+
this.put(TERM_BRACKET)
|
|
938
|
+
let first: boolean = true
|
|
939
|
+
if (styleAttrs === TERM_STYLE_UNKNOWN || (styleAttrs & ~at) !== 0) {
|
|
940
|
+
this.put(TERM_DIGIT_0)
|
|
941
|
+
first = false
|
|
942
|
+
styleAttrs = 0
|
|
943
|
+
styleFg = COLOR_DEFAULT
|
|
944
|
+
styleBg = COLOR_DEFAULT
|
|
945
|
+
}
|
|
946
|
+
if ((at & ATTR_BOLD) !== 0 && (styleAttrs & ATTR_BOLD) === 0) {
|
|
947
|
+
if (!first) {
|
|
948
|
+
this.put(TERM_SEMICOLON)
|
|
949
|
+
}
|
|
950
|
+
this.put(TERM_DIGIT_0 + 1)
|
|
951
|
+
first = false
|
|
952
|
+
}
|
|
953
|
+
if ((at & ATTR_INVERSE) !== 0 && (styleAttrs & ATTR_INVERSE) === 0) {
|
|
954
|
+
if (!first) {
|
|
955
|
+
this.put(TERM_SEMICOLON)
|
|
956
|
+
}
|
|
957
|
+
this.put(TERM_DIGIT_0 + 7)
|
|
958
|
+
first = false
|
|
959
|
+
}
|
|
960
|
+
if (fgKey !== styleFg) {
|
|
961
|
+
if (!first) {
|
|
962
|
+
this.put(TERM_SEMICOLON)
|
|
963
|
+
}
|
|
964
|
+
this.putColour(TERM_SGR_FOREGROUND, fgKey)
|
|
965
|
+
first = false
|
|
966
|
+
}
|
|
967
|
+
if (bgKey !== styleBg) {
|
|
968
|
+
if (!first) {
|
|
969
|
+
this.put(TERM_SEMICOLON)
|
|
970
|
+
}
|
|
971
|
+
this.putColour(TERM_SGR_BACKGROUND, bgKey)
|
|
972
|
+
}
|
|
973
|
+
this.put(TERM_FINAL_STYLE)
|
|
974
|
+
styleFg = fgKey
|
|
975
|
+
styleBg = bgKey
|
|
976
|
+
styleAttrs = at
|
|
977
|
+
}
|
|
978
|
+
this.putGlyph(glyph)
|
|
979
|
+
sentGlyphs[i] = glyph
|
|
980
|
+
sentFgs[i] = fg
|
|
981
|
+
sentBgs[i] = bg
|
|
982
|
+
sentAttrs[i] = at
|
|
983
|
+
cursorRow = row
|
|
984
|
+
cursorCol = col + 1
|
|
985
|
+
if (cursorCol >= w) {
|
|
986
|
+
cursorRow = -1
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
if (
|
|
990
|
+
styleAttrs !== TERM_STYLE_UNKNOWN &&
|
|
991
|
+
(styleAttrs !== 0 || styleFg !== COLOR_DEFAULT || styleBg !== COLOR_DEFAULT)
|
|
992
|
+
) {
|
|
993
|
+
this.put(TERM_ESC)
|
|
994
|
+
this.put(TERM_BRACKET)
|
|
995
|
+
this.put(TERM_DIGIT_0)
|
|
996
|
+
this.put(TERM_FINAL_STYLE)
|
|
997
|
+
}
|
|
998
|
+
if (this.outLen === opened) {
|
|
999
|
+
this.outLen = 0
|
|
1000
|
+
} else if (this.sync) {
|
|
1001
|
+
this.putSyncMode(TERM_FINAL_RESET)
|
|
1002
|
+
}
|
|
1003
|
+
return termBytesText(this.out, this.outLen)
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
/**
|
|
1008
|
+
* Pixels on a `Screen`, two to a cell: `width` is the screen's columns and
|
|
1009
|
+
* `height` twice its rows. Each pixel is a colour or `COLOR_DEFAULT`, which is
|
|
1010
|
+
* the terminal's background showing through. Drawing a pixel redraws its cell
|
|
1011
|
+
* at once, so text drawn after it lands on top, and pixels drawn after text
|
|
1012
|
+
* replace it.
|
|
1013
|
+
*/
|
|
1014
|
+
export class Canvas {
|
|
1015
|
+
/** The screen the pixels are drawn on. */
|
|
1016
|
+
screen: Screen
|
|
1017
|
+
/** Pixels across: the screen's width. */
|
|
1018
|
+
width: i32
|
|
1019
|
+
/** Pixels down: twice the screen's height. */
|
|
1020
|
+
height: i32
|
|
1021
|
+
/** One colour per pixel, row by row. */
|
|
1022
|
+
pixels: i32[]
|
|
1023
|
+
|
|
1024
|
+
/** A canvas of `screen`'s size, every pixel `COLOR_DEFAULT`. Make a new one after the screen is resized. */
|
|
1025
|
+
constructor(screen: Screen) {
|
|
1026
|
+
this.screen = screen
|
|
1027
|
+
this.width = screen.width
|
|
1028
|
+
this.height = screen.height * 2
|
|
1029
|
+
this.pixels = new Array<i32>(this.width * this.height)
|
|
1030
|
+
this.pixels.fill(COLOR_DEFAULT)
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
/** Set every pixel to `colour`. */
|
|
1034
|
+
clear(colour: i32): void {
|
|
1035
|
+
this.pixels.fill(colour)
|
|
1036
|
+
const rows: i32 = this.height >> 1
|
|
1037
|
+
for (let row: i32 = 0; row < rows; row++) {
|
|
1038
|
+
for (let col: i32 = 0; col < this.width; col++) {
|
|
1039
|
+
this.paintCell(col, row)
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
/** The colour of the pixel at `x`, `y`, or `COLOR_DEFAULT` off the canvas. */
|
|
1045
|
+
pixel(x: i32, y: i32): i32 {
|
|
1046
|
+
if (x < 0 || y < 0 || x >= this.width || y >= this.height) {
|
|
1047
|
+
return COLOR_DEFAULT
|
|
1048
|
+
}
|
|
1049
|
+
return this.pixels[y * this.width + x]
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
/** Set the pixel at `x`, `y` to `colour`; a pixel off the canvas is not drawn. */
|
|
1053
|
+
setPixel(x: i32, y: i32, colour: i32): void {
|
|
1054
|
+
if (x < 0 || y < 0 || x >= this.width || y >= this.height) {
|
|
1055
|
+
return
|
|
1056
|
+
}
|
|
1057
|
+
this.pixels[y * this.width + x] = colour
|
|
1058
|
+
this.paintCell(x, y >> 1)
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
/** Fill the `w × h` rectangle whose top-left pixel is `x`, `y`, clipped to the canvas. */
|
|
1062
|
+
fillRect(x: i32, y: i32, w: i32, h: i32, colour: i32): void {
|
|
1063
|
+
const left: i32 = x < 0 ? 0 : x
|
|
1064
|
+
const top: i32 = y < 0 ? 0 : y
|
|
1065
|
+
const right: i32 = x + w > this.width ? this.width : x + w
|
|
1066
|
+
const bottom: i32 = y + h > this.height ? this.height : y + h
|
|
1067
|
+
for (let py: i32 = top; py < bottom; py++) {
|
|
1068
|
+
for (let px: i32 = left; px < right; px++) {
|
|
1069
|
+
this.pixels[py * this.width + px] = colour
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
for (let row: i32 = top >> 1; row < (bottom + 1) >> 1; row++) {
|
|
1073
|
+
for (let col: i32 = left; col < right; col++) {
|
|
1074
|
+
this.paintCell(col, row)
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
/**
|
|
1080
|
+
* Draw a line from `x0`, `y0` to `x1`, `y1`, both ends included (Bresenham's).
|
|
1081
|
+
* Any `i32` ends will do: the walk stops once it leaves the canvas for good,
|
|
1082
|
+
* though one that starts far outside steps all the way in.
|
|
1083
|
+
*/
|
|
1084
|
+
line(x0: i32, y0: i32, x1: i32, y1: i32, colour: i32): void {
|
|
1085
|
+
// The error terms are f64, exact here, because a span past 2^30 overflows `2 * err` in i32.
|
|
1086
|
+
const dx: f64 = Math.abs(toF64(x1) - toF64(x0))
|
|
1087
|
+
const dy: f64 = -Math.abs(toF64(y1) - toF64(y0))
|
|
1088
|
+
const sx: i32 = x0 < x1 ? 1 : -1
|
|
1089
|
+
const sy: i32 = y0 < y1 ? 1 : -1
|
|
1090
|
+
let err: f64 = dx + dy
|
|
1091
|
+
let x: i32 = x0
|
|
1092
|
+
let y: i32 = y0
|
|
1093
|
+
while (true) {
|
|
1094
|
+
this.setPixel(x, y, colour)
|
|
1095
|
+
if (x === x1 && y === y1) {
|
|
1096
|
+
return
|
|
1097
|
+
}
|
|
1098
|
+
const twice: f64 = 2.0 * err
|
|
1099
|
+
if (twice >= dy) {
|
|
1100
|
+
err = err + dy
|
|
1101
|
+
x = x + sx
|
|
1102
|
+
}
|
|
1103
|
+
if (twice <= dx) {
|
|
1104
|
+
err = err + dx
|
|
1105
|
+
y = y + sy
|
|
1106
|
+
}
|
|
1107
|
+
// The walk only moves away from an edge it has passed, so no later point is on the canvas.
|
|
1108
|
+
if (
|
|
1109
|
+
(sx > 0 && x >= this.width) ||
|
|
1110
|
+
(sx < 0 && x < 0) ||
|
|
1111
|
+
(sy > 0 && y >= this.height) ||
|
|
1112
|
+
(sy < 0 && y < 0)
|
|
1113
|
+
) {
|
|
1114
|
+
return
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
/**
|
|
1120
|
+
* Draw `s` in cells from column `col` of cell row `row` in `fg`, each
|
|
1121
|
+
* character on the colour of the pixels behind it (the top one, or the
|
|
1122
|
+
* bottom one where the top is unset), and answer the column after it.
|
|
1123
|
+
*/
|
|
1124
|
+
text(col: i32, row: i32, s: string, fg: i32): i32 {
|
|
1125
|
+
const n: i32 = toI32(s.length)
|
|
1126
|
+
let column: i32 = col
|
|
1127
|
+
let i: i32 = 0
|
|
1128
|
+
while (i < n) {
|
|
1129
|
+
const top: i32 = this.pixel(column, row * 2)
|
|
1130
|
+
const bg: i32 = top >= 0 ? top : this.pixel(column, row * 2 + 1)
|
|
1131
|
+
this.screen.set(column, row, termCodePointAt(s, i, n), fg, bg, ATTR_NONE)
|
|
1132
|
+
i = i + termCodePointUnits(s, i, n)
|
|
1133
|
+
column = column + 1
|
|
1134
|
+
}
|
|
1135
|
+
return column
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
/** Redraw the screen cell at `col`, `row` from its two pixels. */
|
|
1139
|
+
paintCell(col: i32, row: i32): void {
|
|
1140
|
+
const top: i32 = this.pixel(col, row * 2)
|
|
1141
|
+
const bottom: i32 = this.pixel(col, row * 2 + 1)
|
|
1142
|
+
if (top < 0 && bottom < 0) {
|
|
1143
|
+
this.screen.set(col, row, TERM_SPACE, COLOR_DEFAULT, COLOR_DEFAULT, ATTR_NONE)
|
|
1144
|
+
} else if (top < 0) {
|
|
1145
|
+
this.screen.set(col, row, TERM_LOWER_HALF, bottom, COLOR_DEFAULT, ATTR_NONE)
|
|
1146
|
+
} else {
|
|
1147
|
+
this.screen.set(col, row, TERM_UPPER_HALF, top, bottom, ATTR_NONE)
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
/** One key: its `KEY_` kind and a code, which is the code point for `KEY_CHAR` and otherwise the byte that named it. */
|
|
1153
|
+
export class KeyEvent {
|
|
1154
|
+
/** One of the `KEY_` constants. */
|
|
1155
|
+
kind: i32
|
|
1156
|
+
/** The character's code point, the control byte, or the escape sequence's final byte. */
|
|
1157
|
+
code: i32
|
|
1158
|
+
|
|
1159
|
+
/** The event of kind `kind` with code `code`. */
|
|
1160
|
+
constructor(kind: i32, code: i32) {
|
|
1161
|
+
this.kind = kind
|
|
1162
|
+
this.code = code
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
/**
|
|
1167
|
+
* One thing a terminal sent: a key, the mouse, a paste, a focus change or an
|
|
1168
|
+
* answer to a query, told apart by `type` (an `EVENT_` constant). Each type
|
|
1169
|
+
* fills its own fields and leaves the rest at their defaults: 0, -1 for the
|
|
1170
|
+
* pixel position, "" and no params.
|
|
1171
|
+
*/
|
|
1172
|
+
export class TermEvent {
|
|
1173
|
+
/** One of the `EVENT_` constants. */
|
|
1174
|
+
type: i32
|
|
1175
|
+
/** A key's `KEY_` kind. */
|
|
1176
|
+
key: i32
|
|
1177
|
+
/** A key's code: the code point for `KEY_CHAR`, the byte for a control, the kitty key number or the final byte for `KEY_UNKNOWN`. */
|
|
1178
|
+
code: i32
|
|
1179
|
+
/** The `MOD_` bits held with a key or a mouse event. */
|
|
1180
|
+
modifiers: i32
|
|
1181
|
+
/** An `ACTION_` constant: whether a key or button went down, repeated, came up or (the mouse) moved. */
|
|
1182
|
+
action: i32
|
|
1183
|
+
/** The mouse's `MOUSE_` button. */
|
|
1184
|
+
button: i32
|
|
1185
|
+
/** The mouse's column, from 0; -1 under mode 1016 while the cell size is unknown. */
|
|
1186
|
+
x: i32
|
|
1187
|
+
/** The mouse's row, from 0; -1 under mode 1016 while the cell size is unknown. */
|
|
1188
|
+
y: i32
|
|
1189
|
+
/** The mouse's pixel column, from 0, under mode 1016; -1 otherwise. */
|
|
1190
|
+
px: i32
|
|
1191
|
+
/** The mouse's pixel row, from 0, under mode 1016; -1 otherwise. */
|
|
1192
|
+
py: i32
|
|
1193
|
+
/** A paste's text, a kitty key's associated text, or the payload of a reply after its header. */
|
|
1194
|
+
text: string
|
|
1195
|
+
/** A reply's final byte (`c`, `R`, `y`, …); for an APC, its first payload byte (`G` for kitty graphics); for an OSC, 0. */
|
|
1196
|
+
reply: i32
|
|
1197
|
+
/** What opened a reply: `[` for a CSI, `P` for a DCS, `_` for an APC, `]` for an OSC, `^` for a PM, `X` for an SOS. */
|
|
1198
|
+
introducer: i32
|
|
1199
|
+
/** A reply's private marker, `?`, `>`, `=` or `<`, or 0. */
|
|
1200
|
+
prefix: i32
|
|
1201
|
+
/** A reply's intermediate byte, such as the `$` of DECRPM, or 0. */
|
|
1202
|
+
intermediate: i32
|
|
1203
|
+
/** A reply's numeric parameters, an omitted one as 0; for an OSC, its number; for `KEY_UNKNOWN`, the sequence's. */
|
|
1204
|
+
params: i32[]
|
|
1205
|
+
/** For `EVENT_FOCUS`, whether the terminal gained focus. */
|
|
1206
|
+
gained: boolean
|
|
1207
|
+
|
|
1208
|
+
/** An event of type `type` with every other field at its default. */
|
|
1209
|
+
constructor(type: i32) {
|
|
1210
|
+
this.type = type
|
|
1211
|
+
this.key = 0
|
|
1212
|
+
this.code = 0
|
|
1213
|
+
this.modifiers = 0
|
|
1214
|
+
this.action = 0
|
|
1215
|
+
this.button = 0
|
|
1216
|
+
this.x = 0
|
|
1217
|
+
this.y = 0
|
|
1218
|
+
this.px = -1
|
|
1219
|
+
this.py = -1
|
|
1220
|
+
this.text = ""
|
|
1221
|
+
this.reply = 0
|
|
1222
|
+
this.introducer = 0
|
|
1223
|
+
this.prefix = 0
|
|
1224
|
+
this.intermediate = 0
|
|
1225
|
+
this.params = []
|
|
1226
|
+
this.gained = false
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
/**
|
|
1231
|
+
* Bytes from a terminal in raw mode, turned into key events. A read can end
|
|
1232
|
+
* in the middle of an escape sequence or a UTF-8 character; the decoder keeps
|
|
1233
|
+
* the start and finishes it with the next `feed`. An ESC on its own is both the
|
|
1234
|
+
* escape key and the start of every arrow, so it is held until either more
|
|
1235
|
+
* bytes complete a sequence or `escapeTimeoutMs` passes with none, and then it
|
|
1236
|
+
* is `KEY_ESCAPE`. Whatever is held past the timeout is given up the same way:
|
|
1237
|
+
* the ESC as `KEY_ESCAPE`, the bytes after it as keys of their own, and an
|
|
1238
|
+
* unfinished UTF-8 character as U+FFFD. The clock is the caller's: each call
|
|
1239
|
+
* takes the time now in milliseconds, from any origin, so long as it does not
|
|
1240
|
+
* go backwards.
|
|
1241
|
+
*
|
|
1242
|
+
* `feedEvents` and `pollEvents` decode the same bytes into `TermEvent`s
|
|
1243
|
+
* instead: keys with their modifiers and press, repeat or release, the mouse,
|
|
1244
|
+
* pastes, focus and the terminal's replies, none of which reaches the program
|
|
1245
|
+
* as a key. A decoder is fed one way or the other, not both. The module
|
|
1246
|
+
* header lists every sequence they read.
|
|
1247
|
+
*/
|
|
1248
|
+
export class KeyDecoder {
|
|
1249
|
+
/** How long a held ESC waits for the rest of a sequence, in milliseconds. */
|
|
1250
|
+
escapeTimeoutMs: f64
|
|
1251
|
+
/** The bytes of an unfinished sequence, carried to the next call. */
|
|
1252
|
+
pending: u8[]
|
|
1253
|
+
/** When the first held byte arrived (`feed`), or the last (`feedEvents`). */
|
|
1254
|
+
pendingSince: f64
|
|
1255
|
+
/** The bytes of the paste so far. */
|
|
1256
|
+
pasteBytes: u8[]
|
|
1257
|
+
/**
|
|
1258
|
+
* How many cursor position reports (`ESC [ 6 n`) are awaited. `ESC [ r ; c R`
|
|
1259
|
+
* is both the report and F3 with modifiers; while this is above 0 it is the
|
|
1260
|
+
* report, and takes one off. Whoever writes the request adds one.
|
|
1261
|
+
*/
|
|
1262
|
+
cprOutstanding: i32
|
|
1263
|
+
/** A cell's width in pixels, to turn a 1016 report into a column; 0 while unknown. */
|
|
1264
|
+
cellWidthPx: i32
|
|
1265
|
+
/** A cell's height in pixels, to turn a 1016 report into a row; 0 while unknown. */
|
|
1266
|
+
cellHeightPx: i32
|
|
1267
|
+
/** Whether SGR mouse reports are in pixels (mode 1016), so `px` and `py` are set and `x`, `y` derived. */
|
|
1268
|
+
pixelMouse: boolean
|
|
1269
|
+
/** Whether a bracketed paste has begun and not ended. */
|
|
1270
|
+
pasting: boolean
|
|
1271
|
+
|
|
1272
|
+
/** A decoder that gives up on an unfinished sequence after `escapeTimeoutMs` (25–50 is usual). */
|
|
1273
|
+
constructor(escapeTimeoutMs: f64) {
|
|
1274
|
+
this.escapeTimeoutMs = escapeTimeoutMs
|
|
1275
|
+
this.pending = []
|
|
1276
|
+
this.pendingSince = 0.0
|
|
1277
|
+
this.cprOutstanding = 0
|
|
1278
|
+
this.pixelMouse = false
|
|
1279
|
+
this.cellWidthPx = 0
|
|
1280
|
+
this.cellHeightPx = 0
|
|
1281
|
+
this.pasting = false
|
|
1282
|
+
this.pasteBytes = []
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
/** Whether bytes are held, so the caller should `poll` once the timeout has passed. */
|
|
1286
|
+
waiting(): boolean {
|
|
1287
|
+
return toI32(this.pending.length) > 0
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
/**
|
|
1291
|
+
* The keys in `bytes[0, count)`, read at time `nowMs`, after whatever was
|
|
1292
|
+
* held: held bytes older than the timeout are given up first, as keys of
|
|
1293
|
+
* their own, and the rest continue into these.
|
|
1294
|
+
*/
|
|
1295
|
+
feed(bytes: u8[], count: i32, nowMs: f64): KeyEvent[] {
|
|
1296
|
+
const events: KeyEvent[] = this.poll(nowMs)
|
|
1297
|
+
const held: i32 = toI32(this.pending.length)
|
|
1298
|
+
const work: u8[] = this.merged(bytes, count)
|
|
1299
|
+
if (toI32(work.length) === 0) {
|
|
1300
|
+
return events
|
|
1301
|
+
}
|
|
1302
|
+
const used: i32 = termDecodeKeys(work, false, events)
|
|
1303
|
+
// What is held now started in these bytes unless all of it was held before.
|
|
1304
|
+
this.keep(work, used, used >= held, nowMs)
|
|
1305
|
+
return events
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
/** The keys held bytes become once `escapeTimeoutMs` has passed since they arrived: none before then. */
|
|
1309
|
+
poll(nowMs: f64): KeyEvent[] {
|
|
1310
|
+
const events: KeyEvent[] = []
|
|
1311
|
+
const held: i32 = toI32(this.pending.length)
|
|
1312
|
+
if (held === 0 || nowMs - this.pendingSince < this.escapeTimeoutMs) {
|
|
1313
|
+
return events
|
|
1314
|
+
}
|
|
1315
|
+
termDecodeKeys(this.pending, true, events)
|
|
1316
|
+
this.pending = []
|
|
1317
|
+
return events
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
/**
|
|
1321
|
+
* The events in `bytes[0, count)`, read at time `nowMs`, after whatever was
|
|
1322
|
+
* held, as `feed` takes them. The timeout counts from the last byte that
|
|
1323
|
+
* arrived, so a reply split across reads is not given up halfway.
|
|
1324
|
+
*/
|
|
1325
|
+
feedEvents(bytes: u8[], count: i32, nowMs: f64): TermEvent[] {
|
|
1326
|
+
const events: TermEvent[] = this.pollEvents(nowMs)
|
|
1327
|
+
const held: i32 = toI32(this.pending.length)
|
|
1328
|
+
const work: u8[] = this.merged(bytes, count)
|
|
1329
|
+
if (toI32(work.length) === 0) {
|
|
1330
|
+
return events
|
|
1331
|
+
}
|
|
1332
|
+
const used: i32 = termDecodeEvents(this, work, false, events)
|
|
1333
|
+
this.keep(work, used, toI32(work.length) > held, nowMs)
|
|
1334
|
+
return events
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
/**
|
|
1338
|
+
* The events held bytes become once `escapeTimeoutMs` has passed: an ESC as
|
|
1339
|
+
* `KEY_ESCAPE`, an unterminated DCS, APC or OSC as nothing. A paste waits
|
|
1340
|
+
* for its end however long it takes.
|
|
1341
|
+
*/
|
|
1342
|
+
pollEvents(nowMs: f64): TermEvent[] {
|
|
1343
|
+
const events: TermEvent[] = []
|
|
1344
|
+
const held: i32 = toI32(this.pending.length)
|
|
1345
|
+
if (this.pasting || held === 0 || nowMs - this.pendingSince < this.escapeTimeoutMs) {
|
|
1346
|
+
return events
|
|
1347
|
+
}
|
|
1348
|
+
termDecodeEvents(this, this.pending, true, events)
|
|
1349
|
+
this.pending = []
|
|
1350
|
+
return events
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
/** The held bytes followed by `bytes[0, count)`. */
|
|
1354
|
+
merged(bytes: u8[], count: i32): u8[] {
|
|
1355
|
+
const held: i32 = toI32(this.pending.length)
|
|
1356
|
+
const n: i32 = termClamp(count, 0, toI32(bytes.length))
|
|
1357
|
+
const work: u8[] = new Array<u8>(held + n)
|
|
1358
|
+
const pending: u8[] = this.pending
|
|
1359
|
+
for (let k: i32 = 0; k < toI32(pending.length) && k < toI32(work.length); k++) {
|
|
1360
|
+
work[k] = pending[k]
|
|
1361
|
+
}
|
|
1362
|
+
for (let k: i32 = 0; k < n && k < toI32(bytes.length); k++) {
|
|
1363
|
+
work[held + k] = bytes[k]
|
|
1364
|
+
}
|
|
1365
|
+
return work
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
/** Hold `work` from `used` on; when `restart` is set, the held bytes count as arriving at `nowMs`. */
|
|
1369
|
+
keep(work: u8[], used: i32, restart: boolean, nowMs: f64): void {
|
|
1370
|
+
const rest: u8[] = []
|
|
1371
|
+
for (let k: i32 = used; k < toI32(work.length); k++) {
|
|
1372
|
+
if (!(k >= 0)) {
|
|
1373
|
+
panic("nish/term: the key decoder used more bytes than it was given")
|
|
1374
|
+
}
|
|
1375
|
+
rest.push(work[k])
|
|
1376
|
+
}
|
|
1377
|
+
if (toI32(rest.length) > 0 && restart) {
|
|
1378
|
+
this.pendingSince = nowMs
|
|
1379
|
+
}
|
|
1380
|
+
this.pending = rest
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
/**
|
|
1385
|
+
* Which keys are down, from the events a `KeyDecoder` made — what a game asks
|
|
1386
|
+
* each frame. Under the kitty protocol with event types (flag 2) a terminal
|
|
1387
|
+
* sends real releases. Without it a terminal sends nothing when a key comes
|
|
1388
|
+
* up, only the press again as it auto-repeats, so a key is let go once its
|
|
1389
|
+
* repeats stop: `releaseGapMs` after its last repeat, and `firstRepeatMs` after
|
|
1390
|
+
* a press that has not repeated. The system waits before its first repeat —
|
|
1391
|
+
* 660 ms under X11 by default, 500 under Windows, shorter under macOS — and
|
|
1392
|
+
* repeats every 30–40 ms; a longer delay needs `firstRepeatMs` raised, and a
|
|
1393
|
+
* slower rate `releaseGapMs`. Input arrives late and in bursts (over SSH, reads
|
|
1394
|
+
* 150–250 ms apart are usual), so when the reads alone are known the silence
|
|
1395
|
+
* is not: a caller that sends in-order queries calls `delivered(sentAtMs)` as
|
|
1396
|
+
* each is answered, and silence is then measured against that watermark rather
|
|
1397
|
+
* than the clock. A key last seen in a read at `A` is let go once the watermark
|
|
1398
|
+
* reaches `A + gap`: had it still been down, the terminal would have generated
|
|
1399
|
+
* a repeat before then, and it would have arrived ahead of the answer. Until
|
|
1400
|
+
* the first `delivered`, and after the caller clears `watermarked`, the gap is
|
|
1401
|
+
* measured against `nowMs`. Pure, with the caller's clock, as `KeyDecoder` is.
|
|
1402
|
+
*/
|
|
1403
|
+
export class KeyState {
|
|
1404
|
+
/** How long after its last repeat a key is let go, in milliseconds (100 by default). */
|
|
1405
|
+
releaseGapMs: f64
|
|
1406
|
+
/** How long the system waits before a held key's first repeat, at most, in milliseconds (700 by default, above X11's 660). */
|
|
1407
|
+
firstRepeatMs: f64
|
|
1408
|
+
/** The latest send time of an answered query that `delivered` was given: everything the terminal generated before it has arrived. */
|
|
1409
|
+
watermarkMs: f64
|
|
1410
|
+
/** The keys down, each as `termKeyId` names it. */
|
|
1411
|
+
downKeys: i32[]
|
|
1412
|
+
/** When each key down last pressed or repeated. */
|
|
1413
|
+
downSince: f64[]
|
|
1414
|
+
/** Whether each key down has repeated. */
|
|
1415
|
+
downRepeated: boolean[]
|
|
1416
|
+
/** The code each key down last came with, which its synthesized release carries. */
|
|
1417
|
+
downCodes: i32[]
|
|
1418
|
+
/** The `MOD_` bits each key down last came with, which its synthesized release carries. */
|
|
1419
|
+
downMods: i32[]
|
|
1420
|
+
/** Whether releases are made up from silence; false from the first real release, or when the caller knows kitty flag 2 is on. */
|
|
1421
|
+
releasesAreSynthetic: boolean
|
|
1422
|
+
/** Whether silence is measured against `watermarkMs` rather than the clock; set by `delivered`, which the caller may clear when answers stop. */
|
|
1423
|
+
watermarked: boolean
|
|
1424
|
+
/** Whether Ctrl-C (byte 3, or kitty `ESC [ 99 ; 5 u`) becomes `EVENT_QUIT` (true by default). */
|
|
1425
|
+
quitOnCtrlC: boolean
|
|
1426
|
+
|
|
1427
|
+
/** No key down, releases synthesized, Ctrl-C a quit. */
|
|
1428
|
+
constructor() {
|
|
1429
|
+
this.releasesAreSynthetic = true
|
|
1430
|
+
this.releaseGapMs = 100.0
|
|
1431
|
+
this.firstRepeatMs = 700.0
|
|
1432
|
+
this.watermarkMs = 0.0
|
|
1433
|
+
this.watermarked = false
|
|
1434
|
+
this.quitOnCtrlC = true
|
|
1435
|
+
this.downKeys = []
|
|
1436
|
+
this.downSince = []
|
|
1437
|
+
this.downRepeated = []
|
|
1438
|
+
this.downCodes = []
|
|
1439
|
+
this.downMods = []
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
/**
|
|
1443
|
+
* Whether the key of kind `key` is down. `code` tells a `KEY_CHAR`,
|
|
1444
|
+
* `KEY_CONTROL` or `KEY_UNKNOWN` apart and is ignored for the rest; a
|
|
1445
|
+
* letter is the same key in either case, since Shift changes the code a
|
|
1446
|
+
* terminal sends.
|
|
1447
|
+
*/
|
|
1448
|
+
isDown(key: i32, code: i32): boolean {
|
|
1449
|
+
return this.find(termKeyId(key, code)) >= 0
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
/**
|
|
1453
|
+
* Take `events`, which arrived at `nowMs`, and answer what the program
|
|
1454
|
+
* should see: first the releases `tick` makes for the keys `events` does
|
|
1455
|
+
* not mention (a held key's repeats can wait in a read longer than the
|
|
1456
|
+
* gap), then each event, with
|
|
1457
|
+
* Ctrl-C as `EVENT_QUIT` (its repeats and release dropped) and a press of a
|
|
1458
|
+
* key already down marked `ACTION_REPEAT` while releases are synthetic.
|
|
1459
|
+
* A real release of a key not down is dropped: it was let go already, made
|
|
1460
|
+
* up from silence or by losing focus. Losing focus lets every key go, since
|
|
1461
|
+
* its release goes elsewhere. A non-finite `nowMs` releases nothing; a key
|
|
1462
|
+
* it presses keeps its last time, and a new one is timed from the watermark
|
|
1463
|
+
* (0 without one).
|
|
1464
|
+
*/
|
|
1465
|
+
apply(events: TermEvent[], nowMs: f64): TermEvent[] {
|
|
1466
|
+
const out: TermEvent[] = this.releaseDue(nowMs, events)
|
|
1467
|
+
for (let k: i32 = 0; k < toI32(events.length); k++) {
|
|
1468
|
+
const e: TermEvent = events[k]
|
|
1469
|
+
if (e.type === EVENT_FOCUS && !e.gained) {
|
|
1470
|
+
const first: i32 = 0
|
|
1471
|
+
while (toI32(this.downKeys.length) > 0) {
|
|
1472
|
+
this.release(first, out)
|
|
1473
|
+
}
|
|
1474
|
+
} else if (e.type === EVENT_KEY && this.quitOnCtrlC && termIsCtrlC(e)) {
|
|
1475
|
+
if (e.action === ACTION_PRESS) {
|
|
1476
|
+
out.push(new TermEvent(EVENT_QUIT))
|
|
1477
|
+
}
|
|
1478
|
+
continue
|
|
1479
|
+
} else if (e.type === EVENT_KEY && !this.press(e, nowMs)) {
|
|
1480
|
+
continue
|
|
1481
|
+
}
|
|
1482
|
+
out.push(e)
|
|
1483
|
+
}
|
|
1484
|
+
return out
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
/**
|
|
1488
|
+
* Everything the terminal generated before `sentAtMs`, on the caller's
|
|
1489
|
+
* clock, has arrived: the answer to an in-order query sent then has come
|
|
1490
|
+
* back. From now on silence is measured against the latest such time. A
|
|
1491
|
+
* non-finite `sentAtMs` is ignored.
|
|
1492
|
+
*/
|
|
1493
|
+
delivered(sentAtMs: f64): void {
|
|
1494
|
+
if (!termFinite(sentAtMs)) {
|
|
1495
|
+
return
|
|
1496
|
+
}
|
|
1497
|
+
if (!this.watermarked || sentAtMs > this.watermarkMs) {
|
|
1498
|
+
this.watermarkMs = sentAtMs
|
|
1499
|
+
}
|
|
1500
|
+
this.watermarked = true
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
/**
|
|
1504
|
+
* How long before `nowMs` the latest answered query was sent, or -1 with no
|
|
1505
|
+
* watermark: the caller clears `watermarked` once this passes its bound.
|
|
1506
|
+
*/
|
|
1507
|
+
deliveredAgoMs(nowMs: f64): f64 {
|
|
1508
|
+
return this.watermarked ? nowMs - this.watermarkMs : toF64(-1)
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
/** The releases due by `nowMs` while releases are synthetic: each key whose gap has passed, let go. */
|
|
1512
|
+
tick(nowMs: f64): TermEvent[] {
|
|
1513
|
+
const none: TermEvent[] = []
|
|
1514
|
+
return this.releaseDue(nowMs, none)
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
/** The releases due by `nowMs`, as `tick` makes them, for the keys no key event in `batch` names. */
|
|
1518
|
+
releaseDue(nowMs: f64, batch: TermEvent[]): TermEvent[] {
|
|
1519
|
+
const out: TermEvent[] = []
|
|
1520
|
+
if (!this.releasesAreSynthetic || !termFinite(nowMs)) {
|
|
1521
|
+
return out
|
|
1522
|
+
}
|
|
1523
|
+
const clock: f64 = this.watermarked ? this.watermarkMs : nowMs
|
|
1524
|
+
let k: i32 = 0
|
|
1525
|
+
while (k < toI32(this.downKeys.length)) {
|
|
1526
|
+
const gap: f64 = this.downRepeated[k] ? this.releaseGapMs : this.firstRepeatMs
|
|
1527
|
+
if (clock - this.downSince[k] >= gap && !termNamesKey(batch, this.downKeys[k])) {
|
|
1528
|
+
this.release(k, out)
|
|
1529
|
+
} else {
|
|
1530
|
+
k = k + 1
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
return out
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
/** Record key event `e` at `nowMs`; false for a release of a key not down, which `apply` drops. */
|
|
1537
|
+
press(e: TermEvent, nowMs: f64): boolean {
|
|
1538
|
+
const at: i32 = this.find(termKeyId(e.key, e.code))
|
|
1539
|
+
if (e.action === ACTION_RELEASE) {
|
|
1540
|
+
this.releasesAreSynthetic = false
|
|
1541
|
+
if (at < 0) {
|
|
1542
|
+
return false
|
|
1543
|
+
}
|
|
1544
|
+
this.remove(at)
|
|
1545
|
+
return true
|
|
1546
|
+
}
|
|
1547
|
+
const known: boolean = termFinite(nowMs)
|
|
1548
|
+
if (at < 0) {
|
|
1549
|
+
let since: f64 = known ? nowMs : toF64(0)
|
|
1550
|
+
if (!known && this.watermarked) {
|
|
1551
|
+
since = this.watermarkMs
|
|
1552
|
+
}
|
|
1553
|
+
this.downKeys.push(termKeyId(e.key, e.code))
|
|
1554
|
+
this.downSince.push(since)
|
|
1555
|
+
this.downRepeated.push(false)
|
|
1556
|
+
this.downCodes.push(e.code)
|
|
1557
|
+
this.downMods.push(e.modifiers)
|
|
1558
|
+
return true
|
|
1559
|
+
}
|
|
1560
|
+
if (known) {
|
|
1561
|
+
this.downSince[at] = nowMs
|
|
1562
|
+
}
|
|
1563
|
+
this.downRepeated[at] = true
|
|
1564
|
+
this.downCodes[at] = e.code
|
|
1565
|
+
this.downMods[at] = e.modifiers
|
|
1566
|
+
if (this.releasesAreSynthetic) {
|
|
1567
|
+
e.action = ACTION_REPEAT
|
|
1568
|
+
}
|
|
1569
|
+
return true
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
/** Let key `at` go, appending its release to `out`. */
|
|
1573
|
+
release(at: i32, out: TermEvent[]): void {
|
|
1574
|
+
const id: i32 = this.downKeys[at]
|
|
1575
|
+
const e: TermEvent = new TermEvent(EVENT_KEY)
|
|
1576
|
+
e.key = id >> 21
|
|
1577
|
+
e.code = this.downCodes[at]
|
|
1578
|
+
e.modifiers = this.downMods[at]
|
|
1579
|
+
e.action = ACTION_RELEASE
|
|
1580
|
+
out.push(e)
|
|
1581
|
+
this.remove(at)
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
/** Where key `id` is in `downKeys`, or -1. */
|
|
1585
|
+
find(id: i32): i32 {
|
|
1586
|
+
for (let k: i32 = 0; k < toI32(this.downKeys.length); k++) {
|
|
1587
|
+
if (this.downKeys[k] === id) {
|
|
1588
|
+
return k
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
return -1
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
/** Drop key `at`, keeping the others in order. */
|
|
1595
|
+
remove(at: i32): void {
|
|
1596
|
+
const last: i32 = toI32(this.downKeys.length) - 1
|
|
1597
|
+
for (let k: i32 = at; k < last; k++) {
|
|
1598
|
+
this.downKeys[k] = this.downKeys[k + 1]
|
|
1599
|
+
this.downSince[k] = this.downSince[k + 1]
|
|
1600
|
+
this.downRepeated[k] = this.downRepeated[k + 1]
|
|
1601
|
+
this.downCodes[k] = this.downCodes[k + 1]
|
|
1602
|
+
this.downMods[k] = this.downMods[k + 1]
|
|
1603
|
+
}
|
|
1604
|
+
this.downKeys.pop()
|
|
1605
|
+
this.downSince.pop()
|
|
1606
|
+
this.downRepeated.pop()
|
|
1607
|
+
this.downCodes.pop()
|
|
1608
|
+
this.downMods.pop()
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
/**
|
|
1613
|
+
* A fixed-step clock: it turns the time between frames into a whole number of
|
|
1614
|
+
* steps of `1000 / stepsPerSecond` milliseconds, so a game's logic runs at the
|
|
1615
|
+
* same rate whatever the display does. `advance(nowMs)` answers how many steps
|
|
1616
|
+
* to run since the last call and keeps the remainder for the next; `alpha()`
|
|
1617
|
+
* is that remainder as a fraction of a step, to draw between the last two
|
|
1618
|
+
* states. At most `maxSteps` run per call: a clock further behind drops the
|
|
1619
|
+
* backlog rather than spiral, keeping its phase. With `maxSteps` 1 the same
|
|
1620
|
+
* clock paces drawing, answering 1 when a frame is due. The clock is the
|
|
1621
|
+
* caller's, as `KeyDecoder`'s is.
|
|
1622
|
+
*/
|
|
1623
|
+
export class FixedStep {
|
|
1624
|
+
/** The length of one step, in milliseconds. */
|
|
1625
|
+
stepMs: f64
|
|
1626
|
+
/** The time since the last step, in milliseconds, under one step after each `advance`. */
|
|
1627
|
+
carry: f64
|
|
1628
|
+
/** The time the last `advance` was given. */
|
|
1629
|
+
lastMs: f64
|
|
1630
|
+
/** The most steps one `advance` answers. */
|
|
1631
|
+
maxSteps: i32
|
|
1632
|
+
/** Whether `advance` has been called, so `lastMs` is a time. */
|
|
1633
|
+
started: boolean
|
|
1634
|
+
|
|
1635
|
+
/** A clock of `stepsPerSecond` steps a second (1 at least), running at most `maxSteps` (1 at least) per `advance`. */
|
|
1636
|
+
constructor(stepsPerSecond: i32, maxSteps: i32) {
|
|
1637
|
+
const rate: i32 = stepsPerSecond < 1 ? 1 : stepsPerSecond
|
|
1638
|
+
this.stepMs = 1000.0 / toF64(rate)
|
|
1639
|
+
this.maxSteps = maxSteps < 1 ? 1 : maxSteps
|
|
1640
|
+
this.carry = 0.0
|
|
1641
|
+
this.lastMs = 0.0
|
|
1642
|
+
this.started = false
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
/**
|
|
1646
|
+
* The steps to run for the time between the last call and `nowMs`. The first
|
|
1647
|
+
* call only starts the clock and answers 0, as does a time earlier than the
|
|
1648
|
+
* last one.
|
|
1649
|
+
*/
|
|
1650
|
+
advance(nowMs: f64): i32 {
|
|
1651
|
+
if (!this.started || !(nowMs >= this.lastMs)) {
|
|
1652
|
+
this.started = true
|
|
1653
|
+
this.lastMs = nowMs
|
|
1654
|
+
return 0
|
|
1655
|
+
}
|
|
1656
|
+
this.carry = this.carry + (nowMs - this.lastMs)
|
|
1657
|
+
this.lastMs = nowMs
|
|
1658
|
+
let steps: i32 = 0
|
|
1659
|
+
while (this.carry >= this.stepMs && steps < this.maxSteps) {
|
|
1660
|
+
this.carry = this.carry - this.stepMs
|
|
1661
|
+
steps = steps + 1
|
|
1662
|
+
}
|
|
1663
|
+
if (this.carry >= this.stepMs) {
|
|
1664
|
+
this.carry = this.carry - this.stepMs * Math.floor(this.carry / this.stepMs)
|
|
1665
|
+
// Rounding, or a jump to infinity, can leave the remainder outside a step.
|
|
1666
|
+
if (!(this.carry >= 0.0 && this.carry < this.stepMs)) {
|
|
1667
|
+
this.carry = 0.0
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
return steps
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
/** How far into the next step the clock is, from 0 up to but not including 1. */
|
|
1674
|
+
alpha(): f64 {
|
|
1675
|
+
return this.carry / this.stepMs
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
/** The arrow whose final byte is `b` (`A`–`D`), or `KEY_UNKNOWN`. */
|
|
1680
|
+
const termArrowKind = (b: i32): i32 => {
|
|
1681
|
+
if (b === 65) {
|
|
1682
|
+
return KEY_UP
|
|
1683
|
+
}
|
|
1684
|
+
if (b === 66) {
|
|
1685
|
+
return KEY_DOWN
|
|
1686
|
+
}
|
|
1687
|
+
if (b === 67) {
|
|
1688
|
+
return KEY_RIGHT
|
|
1689
|
+
}
|
|
1690
|
+
return b === 68 ? KEY_LEFT : KEY_UNKNOWN
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
/** The key a single byte below 128 is. */
|
|
1694
|
+
const termAsciiKind = (b: i32): i32 => {
|
|
1695
|
+
if (b === 3) {
|
|
1696
|
+
return KEY_CTRL_C
|
|
1697
|
+
}
|
|
1698
|
+
if (b === 13 || b === 10) {
|
|
1699
|
+
return KEY_ENTER
|
|
1700
|
+
}
|
|
1701
|
+
if (b === 32) {
|
|
1702
|
+
return KEY_SPACE
|
|
1703
|
+
}
|
|
1704
|
+
if (b === 9) {
|
|
1705
|
+
return KEY_TAB
|
|
1706
|
+
}
|
|
1707
|
+
if (b === 127 || b === 8) {
|
|
1708
|
+
return KEY_BACKSPACE
|
|
1709
|
+
}
|
|
1710
|
+
return b < 32 ? KEY_CONTROL : KEY_CHAR
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
/**
|
|
1714
|
+
* Decode `bytes` into `events` and answer how many bytes were used.
|
|
1715
|
+
* Unless `last` is set, an unfinished sequence at the end is left unused for
|
|
1716
|
+
* the caller to hold; with it set, the unfinished start is given up.
|
|
1717
|
+
*/
|
|
1718
|
+
const termDecodeKeys = (bytes: u8[], last: boolean, events: KeyEvent[]): i32 => {
|
|
1719
|
+
let i: i32 = 0
|
|
1720
|
+
while (i < toI32(bytes.length)) {
|
|
1721
|
+
if (!(i >= 0)) {
|
|
1722
|
+
panic("nish/term: the key decoder stepped back")
|
|
1723
|
+
}
|
|
1724
|
+
const b: i32 = toI32(bytes[i])
|
|
1725
|
+
if (b === TERM_ESC) {
|
|
1726
|
+
if (i + 1 >= toI32(bytes.length)) {
|
|
1727
|
+
if (!last) {
|
|
1728
|
+
return i
|
|
1729
|
+
}
|
|
1730
|
+
events.push(new KeyEvent(KEY_ESCAPE, b))
|
|
1731
|
+
i = i + 1
|
|
1732
|
+
continue
|
|
1733
|
+
}
|
|
1734
|
+
const next: i32 = toI32(bytes[i + 1])
|
|
1735
|
+
if (next === 91) {
|
|
1736
|
+
let j: i32 = i + 2
|
|
1737
|
+
const stop: i32 = j + TERM_MAX_CSI_PARAMS + 1
|
|
1738
|
+
while (j < toI32(bytes.length) && j < stop && toI32(bytes[j]) >= 32 && toI32(bytes[j]) < 64) {
|
|
1739
|
+
j = j + 1
|
|
1740
|
+
}
|
|
1741
|
+
// A run past the cap is no key a terminal sends: give the ESC up now
|
|
1742
|
+
// rather than hold, and copy on every `feed`, what follows.
|
|
1743
|
+
if (j === stop) {
|
|
1744
|
+
events.push(new KeyEvent(KEY_ESCAPE, b))
|
|
1745
|
+
i = i + 1
|
|
1746
|
+
continue
|
|
1747
|
+
}
|
|
1748
|
+
if (j >= toI32(bytes.length)) {
|
|
1749
|
+
if (!last) {
|
|
1750
|
+
return i
|
|
1751
|
+
}
|
|
1752
|
+
events.push(new KeyEvent(KEY_ESCAPE, b))
|
|
1753
|
+
i = i + 1
|
|
1754
|
+
continue
|
|
1755
|
+
}
|
|
1756
|
+
const final: i32 = toI32(bytes[j])
|
|
1757
|
+
if (final >= 64 && final < 127) {
|
|
1758
|
+
events.push(new KeyEvent(termArrowKind(final), final))
|
|
1759
|
+
i = j + 1
|
|
1760
|
+
} else {
|
|
1761
|
+
events.push(new KeyEvent(KEY_ESCAPE, b))
|
|
1762
|
+
i = i + 1
|
|
1763
|
+
}
|
|
1764
|
+
continue
|
|
1765
|
+
}
|
|
1766
|
+
if (next === 79) {
|
|
1767
|
+
if (i + 2 >= toI32(bytes.length)) {
|
|
1768
|
+
if (!last) {
|
|
1769
|
+
return i
|
|
1770
|
+
}
|
|
1771
|
+
events.push(new KeyEvent(KEY_ESCAPE, b))
|
|
1772
|
+
i = i + 1
|
|
1773
|
+
continue
|
|
1774
|
+
}
|
|
1775
|
+
const final: i32 = toI32(bytes[i + 2])
|
|
1776
|
+
if (final >= 64 && final < 127) {
|
|
1777
|
+
events.push(new KeyEvent(termArrowKind(final), final))
|
|
1778
|
+
i = i + 3
|
|
1779
|
+
} else {
|
|
1780
|
+
events.push(new KeyEvent(KEY_ESCAPE, b))
|
|
1781
|
+
i = i + 1
|
|
1782
|
+
}
|
|
1783
|
+
continue
|
|
1784
|
+
}
|
|
1785
|
+
events.push(new KeyEvent(KEY_ESCAPE, b))
|
|
1786
|
+
i = i + 1
|
|
1787
|
+
continue
|
|
1788
|
+
}
|
|
1789
|
+
if (b < 128) {
|
|
1790
|
+
events.push(new KeyEvent(termAsciiKind(b), b))
|
|
1791
|
+
i = i + 1
|
|
1792
|
+
continue
|
|
1793
|
+
}
|
|
1794
|
+
const char: i32 = termUtf8At(bytes, i, last)
|
|
1795
|
+
if (char < 0) {
|
|
1796
|
+
return i
|
|
1797
|
+
}
|
|
1798
|
+
events.push(new KeyEvent(KEY_CHAR, char >> 3))
|
|
1799
|
+
i = i + (char & 7)
|
|
1800
|
+
}
|
|
1801
|
+
return toI32(bytes.length)
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
/**
|
|
1805
|
+
* The UTF-8 character at `bytes[i]` (a byte of 128 or more), as its code
|
|
1806
|
+
* point shifted left 3 with the bytes it used in the low 3 bits; -1 when it
|
|
1807
|
+
* is unfinished at the end and `last` is not set, so the caller holds it. A
|
|
1808
|
+
* byte that starts no character, a broken sequence, a C1 control, an overlong
|
|
1809
|
+
* form, a surrogate or a code point past U+10FFFF is U+FFFD.
|
|
1810
|
+
*/
|
|
1811
|
+
const termUtf8At = (bytes: u8[], i: i32, last: boolean): i32 => {
|
|
1812
|
+
const b: i32 = toI32(bytes[i])
|
|
1813
|
+
// 0xC0 and 0xC1 only ever start an overlong form, and 0xF5 up a code
|
|
1814
|
+
// point past U+10FFFF or nothing.
|
|
1815
|
+
const width: i32 = b < 194 || b >= 245 ? 1 : termLeadWidth(b)
|
|
1816
|
+
if (width === 1) {
|
|
1817
|
+
return (TERM_REPLACEMENT << 3) | 1
|
|
1818
|
+
}
|
|
1819
|
+
let cp: i32 = termLeadBits(b, width)
|
|
1820
|
+
let good: i32 = 0
|
|
1821
|
+
let stopped: boolean = false
|
|
1822
|
+
for (let k: i32 = 1; k < width && !stopped; k++) {
|
|
1823
|
+
if (i + k >= toI32(bytes.length)) {
|
|
1824
|
+
stopped = true
|
|
1825
|
+
} else {
|
|
1826
|
+
const c: i32 = toI32(bytes[i + k])
|
|
1827
|
+
if (c < 128 || c >= 192) {
|
|
1828
|
+
stopped = true
|
|
1829
|
+
} else {
|
|
1830
|
+
cp = (cp << 6) | (c & 63)
|
|
1831
|
+
good = good + 1
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
if (good < width - 1) {
|
|
1836
|
+
if (!last && i + 1 + good >= toI32(bytes.length)) {
|
|
1837
|
+
return -1
|
|
1838
|
+
}
|
|
1839
|
+
return (TERM_REPLACEMENT << 3) | (1 + good)
|
|
1840
|
+
}
|
|
1841
|
+
let least: i32 = 65536
|
|
1842
|
+
if (width === 2) {
|
|
1843
|
+
least = 160
|
|
1844
|
+
} else if (width === 3) {
|
|
1845
|
+
least = 2048
|
|
1846
|
+
}
|
|
1847
|
+
const scalar: boolean = cp >= least && cp < 1114112 && !(cp >= 55296 && cp < 57344)
|
|
1848
|
+
return ((scalar ? cp : TERM_REPLACEMENT) << 3) | width
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
/** The key event of kind `key` with code `code`, modifiers `mods` and action `action`. */
|
|
1852
|
+
const termKeyEvent = (key: i32, code: i32, mods: i32, action: i32): TermEvent => {
|
|
1853
|
+
const e: TermEvent = new TermEvent(EVENT_KEY)
|
|
1854
|
+
e.key = key
|
|
1855
|
+
e.code = code
|
|
1856
|
+
e.modifiers = mods
|
|
1857
|
+
e.action = action
|
|
1858
|
+
return e
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
/** The `MOD_` bits of a kitty or xterm modifier parameter `raw` (1 + the bits; caps and num lock dropped), 0 when omitted. */
|
|
1862
|
+
const termModifiers = (raw: i32): i32 => (raw < 1 ? 0 : (raw - 1) & 63)
|
|
1863
|
+
|
|
1864
|
+
/** The `ACTION_` of a kitty event type `raw`: 2 a repeat, 3 a release, anything else a press. */
|
|
1865
|
+
const termAction = (raw: i32): i32 => {
|
|
1866
|
+
if (raw === 2) {
|
|
1867
|
+
return ACTION_REPEAT
|
|
1868
|
+
}
|
|
1869
|
+
return raw === 3 ? ACTION_RELEASE : ACTION_PRESS
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
/**
|
|
1873
|
+
* Field `field`, sub-field `sub` of the parameters `bytes[from, to)`, where
|
|
1874
|
+
* `;` separates fields and `:` sub-fields (ECMA-48 § 5.4.2; the kitty
|
|
1875
|
+
* protocol's `code:shifted ; mods:event ; text`); -1 when it is omitted.
|
|
1876
|
+
*/
|
|
1877
|
+
const termCsiField = (bytes: u8[], from: i32, to: i32, field: i32, sub: i32): i32 => {
|
|
1878
|
+
let f: i32 = 0
|
|
1879
|
+
let s: i32 = 0
|
|
1880
|
+
let value: i32 = -1
|
|
1881
|
+
for (let k: i32 = from; k < to && k < toI32(bytes.length); k++) {
|
|
1882
|
+
const c: i32 = toI32(bytes[k])
|
|
1883
|
+
if (c === TERM_SEMICOLON) {
|
|
1884
|
+
f = f + 1
|
|
1885
|
+
s = 0
|
|
1886
|
+
} else if (c === 58) {
|
|
1887
|
+
s = s + 1
|
|
1888
|
+
} else if (c >= TERM_DIGIT_0 && c < TERM_DIGIT_0 + 10 && f === field && s === sub) {
|
|
1889
|
+
// Past eight digits a number is no parameter a terminal sends; it stops growing.
|
|
1890
|
+
if (value < 0) {
|
|
1891
|
+
value = c - TERM_DIGIT_0
|
|
1892
|
+
} else if (value < 10000000) {
|
|
1893
|
+
value = value * 10 + (c - TERM_DIGIT_0)
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
return value
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1900
|
+
/** The first sub-field of each field of the parameters `bytes[from, to)`, an omitted one as 0. */
|
|
1901
|
+
const termCsiParams = (bytes: u8[], from: i32, to: i32): i32[] => {
|
|
1902
|
+
const params: i32[] = []
|
|
1903
|
+
if (from >= to) {
|
|
1904
|
+
return params
|
|
1905
|
+
}
|
|
1906
|
+
let fields: i32 = 1
|
|
1907
|
+
for (let k: i32 = from; k < to && k < toI32(bytes.length); k++) {
|
|
1908
|
+
if (toI32(bytes[k]) === TERM_SEMICOLON) {
|
|
1909
|
+
fields = fields + 1
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
for (let f: i32 = 0; f < fields; f++) {
|
|
1913
|
+
const v: i32 = termCsiField(bytes, from, to, f, 0)
|
|
1914
|
+
params.push(v < 0 ? 0 : v)
|
|
1915
|
+
}
|
|
1916
|
+
return params
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
/** The string of UTF-8 `bytes[from, to)`, as `termBytesText` makes one. */
|
|
1920
|
+
const termBytesRange = (bytes: u8[], from: i32, to: i32): string => {
|
|
1921
|
+
const part: u8[] = []
|
|
1922
|
+
for (let k: i32 = from; k < to && k < toI32(bytes.length); k++) {
|
|
1923
|
+
part.push(bytes[k])
|
|
1924
|
+
}
|
|
1925
|
+
return termBytesText(part, toI32(part.length))
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
/** Append code point `cp` to `out` as UTF-8; one that is no scalar value goes as U+FFFD. */
|
|
1929
|
+
const termPushUtf8 = (out: u8[], cp: i32): void => {
|
|
1930
|
+
const c: i32 = cp >= 0 && cp < 1114112 && !(cp >= 55296 && cp < 57344) ? cp : TERM_REPLACEMENT
|
|
1931
|
+
if (c < 128) {
|
|
1932
|
+
out.push(toU8(c))
|
|
1933
|
+
} else if (c < 2048) {
|
|
1934
|
+
out.push(toU8((c >> 6) | 192))
|
|
1935
|
+
out.push(toU8((c & 63) | 128))
|
|
1936
|
+
} else if (c < 65536) {
|
|
1937
|
+
out.push(toU8((c >> 12) | 224))
|
|
1938
|
+
out.push(toU8(((c >> 6) & 63) | 128))
|
|
1939
|
+
out.push(toU8((c & 63) | 128))
|
|
1940
|
+
} else {
|
|
1941
|
+
out.push(toU8((c >> 18) | 240))
|
|
1942
|
+
out.push(toU8(((c >> 12) & 63) | 128))
|
|
1943
|
+
out.push(toU8(((c >> 6) & 63) | 128))
|
|
1944
|
+
out.push(toU8((c & 63) | 128))
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
/**
|
|
1949
|
+
* The key a kitty `ESC [ code u` names (kitty keyboard protocol, "An
|
|
1950
|
+
* overview"): Enter, Tab, Backspace, Escape and Space by their codes, another
|
|
1951
|
+
* control below 32 as itself, a key in the Private Use Area (57344–63743,
|
|
1952
|
+
* kitty's functional keys: keypad, media, modifiers, F13 up) as `KEY_UNKNOWN`,
|
|
1953
|
+
* and any other code point as the character.
|
|
1954
|
+
*/
|
|
1955
|
+
const termKittyKind = (code: i32): i32 => {
|
|
1956
|
+
if (code === 13) {
|
|
1957
|
+
return KEY_ENTER
|
|
1958
|
+
}
|
|
1959
|
+
if (code === 9) {
|
|
1960
|
+
return KEY_TAB
|
|
1961
|
+
}
|
|
1962
|
+
if (code === 127 || code === 8) {
|
|
1963
|
+
return KEY_BACKSPACE
|
|
1964
|
+
}
|
|
1965
|
+
if (code === TERM_ESC) {
|
|
1966
|
+
return KEY_ESCAPE
|
|
1967
|
+
}
|
|
1968
|
+
if (code === TERM_SPACE) {
|
|
1969
|
+
return KEY_SPACE
|
|
1970
|
+
}
|
|
1971
|
+
if (code < 0 || (code >= 57344 && code < 63744) || code >= 1114112) {
|
|
1972
|
+
return KEY_UNKNOWN
|
|
1973
|
+
}
|
|
1974
|
+
return code < TERM_SPACE ? KEY_CONTROL : KEY_CHAR
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
/**
|
|
1978
|
+
* The key `ESC [ n ~` names (xterm ctlseqs, "PC-Style Function Keys"; the
|
|
1979
|
+
* kitty protocol keeps them): 1 and 7 Home, 2 Insert, 3 Delete, 4 and 8 End,
|
|
1980
|
+
* 5 Page Up, 6 Page Down, 11–15 F1–F5, 17–21 F6–F10, 23 F11, 24 F12.
|
|
1981
|
+
*/
|
|
1982
|
+
const termTildeKind = (n: i32): i32 => {
|
|
1983
|
+
if (n === 1 || n === 7) {
|
|
1984
|
+
return KEY_HOME
|
|
1985
|
+
}
|
|
1986
|
+
if (n === 4 || n === 8) {
|
|
1987
|
+
return KEY_END
|
|
1988
|
+
}
|
|
1989
|
+
if (n === 2) {
|
|
1990
|
+
return KEY_INSERT
|
|
1991
|
+
}
|
|
1992
|
+
if (n === 3) {
|
|
1993
|
+
return KEY_DELETE
|
|
1994
|
+
}
|
|
1995
|
+
if (n === 5) {
|
|
1996
|
+
return KEY_PAGE_UP
|
|
1997
|
+
}
|
|
1998
|
+
if (n === 6) {
|
|
1999
|
+
return KEY_PAGE_DOWN
|
|
2000
|
+
}
|
|
2001
|
+
if (n >= 11 && n <= 15) {
|
|
2002
|
+
return KEY_F1 + (n - 11)
|
|
2003
|
+
}
|
|
2004
|
+
if (n >= 17 && n <= 21) {
|
|
2005
|
+
return KEY_F6 + (n - 17)
|
|
2006
|
+
}
|
|
2007
|
+
if (n === 23 || n === 24) {
|
|
2008
|
+
return KEY_F11 + (n - 23)
|
|
2009
|
+
}
|
|
2010
|
+
return KEY_UNKNOWN
|
|
2011
|
+
}
|
|
2012
|
+
|
|
2013
|
+
/**
|
|
2014
|
+
* The key a letter final byte names, `ESC [ 1 ; m X` or `ESC O X` (xterm
|
|
2015
|
+
* ctlseqs, "PC-Style Function Keys"): A–D the arrows, H Home, F End, P–S
|
|
2016
|
+
* F1–F4.
|
|
2017
|
+
*/
|
|
2018
|
+
const termLetterKind = (f: i32): i32 => {
|
|
2019
|
+
if (f === 72) {
|
|
2020
|
+
return KEY_HOME
|
|
2021
|
+
}
|
|
2022
|
+
if (f === 70) {
|
|
2023
|
+
return KEY_END
|
|
2024
|
+
}
|
|
2025
|
+
if (f >= 80 && f <= 83) {
|
|
2026
|
+
return KEY_F1 + (f - 80)
|
|
2027
|
+
}
|
|
2028
|
+
return termArrowKind(f)
|
|
2029
|
+
}
|
|
2030
|
+
|
|
2031
|
+
/** Whether key event `e` is Ctrl-C: byte 3, or kitty's `c` with Ctrl alone. */
|
|
2032
|
+
/** Whether `x` is a number other than NaN and the infinities, for which `x - x` is NaN. */
|
|
2033
|
+
const termFinite = (x: f64): boolean => x - x === 0.0
|
|
2034
|
+
|
|
2035
|
+
const termIsCtrlC = (e: TermEvent): boolean =>
|
|
2036
|
+
e.key === KEY_CTRL_C || (e.key === KEY_CHAR && e.code === 99 && e.modifiers === MOD_CTRL)
|
|
2037
|
+
|
|
2038
|
+
/** Whether a key event in `batch` is of key `id`, as `termKeyId` names it. */
|
|
2039
|
+
const termNamesKey = (batch: TermEvent[], id: i32): boolean => {
|
|
2040
|
+
for (let k: i32 = 0; k < toI32(batch.length); k++) {
|
|
2041
|
+
const e: TermEvent = batch[k]
|
|
2042
|
+
if (e.type === EVENT_KEY && termKeyId(e.key, e.code) === id) {
|
|
2043
|
+
return true
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
return false
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
/** One number for a key `KeyState` tracks: its kind above bit 21, and the code below for the kinds a code tells apart, a capital folded to its small letter. */
|
|
2050
|
+
const termKeyId = (key: i32, code: i32): i32 => {
|
|
2051
|
+
let c: i32 = key === KEY_CHAR || key === KEY_CONTROL || key === KEY_UNKNOWN ? code : 0
|
|
2052
|
+
if (key === KEY_CHAR && c >= 65 && c <= 90) {
|
|
2053
|
+
c = c + 32
|
|
2054
|
+
}
|
|
2055
|
+
return (key << 21) | (c & 2097151)
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2058
|
+
/** The byte at `k` of the paste terminator `ESC [ 2 0 1 ~` (xterm ctlseqs, "Bracketed Paste Mode"). */
|
|
2059
|
+
const termPasteEndByte = (k: i32): i32 => {
|
|
2060
|
+
if (k === 0) {
|
|
2061
|
+
return TERM_ESC
|
|
2062
|
+
}
|
|
2063
|
+
if (k === 1) {
|
|
2064
|
+
return TERM_BRACKET
|
|
2065
|
+
}
|
|
2066
|
+
if (k === 5) {
|
|
2067
|
+
return 126
|
|
2068
|
+
}
|
|
2069
|
+
if (k === 3) {
|
|
2070
|
+
return TERM_DIGIT_0
|
|
2071
|
+
}
|
|
2072
|
+
return k === 2 ? 50 : 49
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
/**
|
|
2076
|
+
* Carry a bracketed paste on from `bytes[i]`: append to the paste up to its
|
|
2077
|
+
* terminator and answer where decoding resumes after it, with the paste as
|
|
2078
|
+
* one `EVENT_PASTE`. A terminator cut off at the end is held unless `last`;
|
|
2079
|
+
* without one, every byte joins the paste. ESC, newlines and anything else
|
|
2080
|
+
* inside are text, never keys.
|
|
2081
|
+
*/
|
|
2082
|
+
const termDecodePaste = (d: KeyDecoder, bytes: u8[], i: i32, last: boolean, events: TermEvent[]): i32 => {
|
|
2083
|
+
const n: i32 = toI32(bytes.length)
|
|
2084
|
+
let k: i32 = i
|
|
2085
|
+
while (k >= 0 && k < toI32(bytes.length)) {
|
|
2086
|
+
let m: i32 = 0
|
|
2087
|
+
while (m < 6 && k + m < n && toI32(bytes[k + m]) === termPasteEndByte(m)) {
|
|
2088
|
+
m = m + 1
|
|
2089
|
+
}
|
|
2090
|
+
if (m === 6 || (m > 0 && k + m === n && !last)) {
|
|
2091
|
+
break
|
|
2092
|
+
}
|
|
2093
|
+
d.pasteBytes.push(bytes[k])
|
|
2094
|
+
k = k + 1
|
|
2095
|
+
}
|
|
2096
|
+
if (k >= n || k + 6 > n) {
|
|
2097
|
+
return k
|
|
2098
|
+
}
|
|
2099
|
+
const e: TermEvent = new TermEvent(EVENT_PASTE)
|
|
2100
|
+
e.text = termBytesText(d.pasteBytes, toI32(d.pasteBytes.length))
|
|
2101
|
+
events.push(e)
|
|
2102
|
+
d.pasting = false
|
|
2103
|
+
d.pasteBytes = []
|
|
2104
|
+
return k + 6
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
/**
|
|
2108
|
+
* The mouse report `ESC [ < b ; x ; y M` (press) or `m` (release), SGR mode
|
|
2109
|
+
* 1006 or, in pixels, 1016 (xterm ctlseqs, "Mouse Tracking", "Extended
|
|
2110
|
+
* coordinates"). In `b` the low two bits are the button (3 is none), +4
|
|
2111
|
+
* Shift, +8 Alt, +16 Ctrl, +32 a move, +64 the wheel and +128 buttons 8–11;
|
|
2112
|
+
* `x` and `y` count from 1, made to count from 0.
|
|
2113
|
+
*/
|
|
2114
|
+
const termMouseEvent = (d: KeyDecoder, bytes: u8[], from: i32, to: i32, final: i32): TermEvent => {
|
|
2115
|
+
const b: i32 = termCsiField(bytes, from, to, 0, 0)
|
|
2116
|
+
const x: i32 = termClamp(termCsiField(bytes, from, to, 1, 0) - 1, 0, 2147483646)
|
|
2117
|
+
const y: i32 = termClamp(termCsiField(bytes, from, to, 2, 0) - 1, 0, 2147483646)
|
|
2118
|
+
const e: TermEvent = new TermEvent(EVENT_MOUSE)
|
|
2119
|
+
const bits: i32 = b < 0 ? 3 : b
|
|
2120
|
+
const none: i32 = 0
|
|
2121
|
+
e.modifiers =
|
|
2122
|
+
((bits & 4) !== 0 ? MOD_SHIFT : none) |
|
|
2123
|
+
((bits & 8) !== 0 ? MOD_ALT : none) |
|
|
2124
|
+
((bits & 16) !== 0 ? MOD_CTRL : none)
|
|
2125
|
+
const low: i32 = bits & 3
|
|
2126
|
+
if ((bits & 128) !== 0) {
|
|
2127
|
+
e.button = MOUSE_BUTTON_8 + low
|
|
2128
|
+
} else if ((bits & 64) !== 0) {
|
|
2129
|
+
e.button = MOUSE_WHEEL_UP + low
|
|
2130
|
+
} else {
|
|
2131
|
+
e.button = low === 3 ? MOUSE_NONE : low + 1
|
|
2132
|
+
}
|
|
2133
|
+
if ((bits & 32) !== 0) {
|
|
2134
|
+
e.action = ACTION_MOVE
|
|
2135
|
+
} else {
|
|
2136
|
+
e.action = final === 77 ? ACTION_PRESS : ACTION_RELEASE
|
|
2137
|
+
}
|
|
2138
|
+
if (!d.pixelMouse) {
|
|
2139
|
+
e.x = x
|
|
2140
|
+
e.y = y
|
|
2141
|
+
return e
|
|
2142
|
+
}
|
|
2143
|
+
e.px = x
|
|
2144
|
+
e.py = y
|
|
2145
|
+
e.x = d.cellWidthPx > 0 ? (x / d.cellWidthPx) | 0 : -1
|
|
2146
|
+
e.y = d.cellHeightPx > 0 ? (y / d.cellHeightPx) | 0 : -1
|
|
2147
|
+
return e
|
|
2148
|
+
}
|
|
2149
|
+
|
|
2150
|
+
/** A reply opened by `introducer`, with `prefix`, `params`, `intermediate`, final byte `reply` and `text`. */
|
|
2151
|
+
const termReplyEvent = (
|
|
2152
|
+
introducer: i32,
|
|
2153
|
+
prefix: i32,
|
|
2154
|
+
params: i32[],
|
|
2155
|
+
intermediate: i32,
|
|
2156
|
+
reply: i32,
|
|
2157
|
+
text: string
|
|
2158
|
+
): TermEvent => {
|
|
2159
|
+
const e: TermEvent = new TermEvent(EVENT_REPLY)
|
|
2160
|
+
e.introducer = introducer
|
|
2161
|
+
e.prefix = prefix
|
|
2162
|
+
e.params = params
|
|
2163
|
+
e.intermediate = intermediate
|
|
2164
|
+
e.reply = reply
|
|
2165
|
+
e.text = text
|
|
2166
|
+
return e
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
/**
|
|
2170
|
+
* The event of the complete control sequence `ESC [ prefix params
|
|
2171
|
+
* intermediate final`, its parameters in `bytes[from, to)`, appended to
|
|
2172
|
+
* `events` — or, for `ESC [ 200 ~`, the start of a paste, and for a stray
|
|
2173
|
+
* `ESC [ 201 ~`, nothing.
|
|
2174
|
+
*/
|
|
2175
|
+
const termCsiEvent = (
|
|
2176
|
+
d: KeyDecoder,
|
|
2177
|
+
bytes: u8[],
|
|
2178
|
+
from: i32,
|
|
2179
|
+
to: i32,
|
|
2180
|
+
prefix: i32,
|
|
2181
|
+
intermediate: i32,
|
|
2182
|
+
final: i32,
|
|
2183
|
+
events: TermEvent[]
|
|
2184
|
+
): void => {
|
|
2185
|
+
if (prefix === 60 && (final === 77 || final === 109)) {
|
|
2186
|
+
events.push(termMouseEvent(d, bytes, from, to, final))
|
|
2187
|
+
return
|
|
2188
|
+
}
|
|
2189
|
+
const first: i32 = termCsiField(bytes, from, to, 0, 0)
|
|
2190
|
+
// DA1 `ESC [ ? … c`, DA2 `ESC [ > … c`, DECRPM `ESC [ ? n ; v $ y`, kitty's
|
|
2191
|
+
// flags `ESC [ ? f u`, XTSMGRAPHICS `ESC [ ? i ; s ; v S`, DECXCPR: anything
|
|
2192
|
+
// with a private marker or an intermediate is an answer, never a key. DSR
|
|
2193
|
+
// `ESC [ 0 n` and the window reports `ESC [ 4|6|8 ; a ; b t` have no marker
|
|
2194
|
+
// but a final no key uses (xterm ctlseqs, "Functions using CSI").
|
|
2195
|
+
const cpr: boolean = final === 82 && d.cprOutstanding > 0
|
|
2196
|
+
if (prefix !== 0 || intermediate !== 0 || final === 110 || final === 116 || cpr) {
|
|
2197
|
+
if (cpr) {
|
|
2198
|
+
d.cprOutstanding = d.cprOutstanding - 1
|
|
2199
|
+
}
|
|
2200
|
+
events.push(termReplyEvent(TERM_BRACKET, prefix, termCsiParams(bytes, from, to), intermediate, final, ""))
|
|
2201
|
+
return
|
|
2202
|
+
}
|
|
2203
|
+
// Focus in `ESC [ I` and out `ESC [ O`, mode 1004 (xterm ctlseqs, "FocusIn/FocusOut").
|
|
2204
|
+
if ((final === 73 || final === 79) && from === to) {
|
|
2205
|
+
const e: TermEvent = new TermEvent(EVENT_FOCUS)
|
|
2206
|
+
e.gained = final === 73
|
|
2207
|
+
events.push(e)
|
|
2208
|
+
return
|
|
2209
|
+
}
|
|
2210
|
+
if (final === 126 && (first === 200 || first === 201)) {
|
|
2211
|
+
d.pasting = first === 200
|
|
2212
|
+
d.pasteBytes = []
|
|
2213
|
+
return
|
|
2214
|
+
}
|
|
2215
|
+
const mods: i32 = termModifiers(termCsiField(bytes, from, to, 1, 0))
|
|
2216
|
+
const action: i32 = termAction(termCsiField(bytes, from, to, 1, 1))
|
|
2217
|
+
// kitty `ESC [ code ; mods:event ; text u` (kitty keyboard protocol,
|
|
2218
|
+
// "Key events"), and xterm's modifyOtherKeys `ESC [ 27 ; mods ; code ~`.
|
|
2219
|
+
if (final === 117 || (final === 126 && first === 27)) {
|
|
2220
|
+
const code: i32 = final === 117 ? first : termCsiField(bytes, from, to, 2, 0)
|
|
2221
|
+
const e: TermEvent = termKeyEvent(termKittyKind(code), code < 0 ? final : code, mods, action)
|
|
2222
|
+
if (final === 117) {
|
|
2223
|
+
const text: u8[] = []
|
|
2224
|
+
for (let s: i32 = 0; termCsiField(bytes, from, to, 2, s) >= 0; s++) {
|
|
2225
|
+
termPushUtf8(text, termCsiField(bytes, from, to, 2, s))
|
|
2226
|
+
}
|
|
2227
|
+
e.text = termBytesText(text, toI32(text.length))
|
|
2228
|
+
}
|
|
2229
|
+
events.push(e)
|
|
2230
|
+
return
|
|
2231
|
+
}
|
|
2232
|
+
// Shift-Tab, `ESC [ Z` (xterm ctlseqs, "CBT").
|
|
2233
|
+
if (final === 90) {
|
|
2234
|
+
events.push(termKeyEvent(KEY_TAB, 9, mods | MOD_SHIFT, action))
|
|
2235
|
+
return
|
|
2236
|
+
}
|
|
2237
|
+
const kind: i32 = final === 126 ? termTildeKind(first) : termLetterKind(final)
|
|
2238
|
+
const e: TermEvent = termKeyEvent(kind, final, mods, action)
|
|
2239
|
+
if (kind === KEY_UNKNOWN) {
|
|
2240
|
+
e.params = termCsiParams(bytes, from, to)
|
|
2241
|
+
}
|
|
2242
|
+
events.push(e)
|
|
2243
|
+
}
|
|
2244
|
+
|
|
2245
|
+
/**
|
|
2246
|
+
* Decode the control sequence at `bytes[i]` (`ESC [`) into `events` and answer
|
|
2247
|
+
* where decoding resumes, or -1 when it is unfinished at the end and `last`
|
|
2248
|
+
* is not set. Parameter and intermediate bytes run 32–63 and a final byte
|
|
2249
|
+
* 64–126 (ECMA-48 § 5.4); a sequence broken off by another byte is an ESC
|
|
2250
|
+
* key, and the bytes after it keys of their own, as `feed` has it.
|
|
2251
|
+
*/
|
|
2252
|
+
const termDecodeCsi = (d: KeyDecoder, bytes: u8[], i: i32, last: boolean, events: TermEvent[]): i32 => {
|
|
2253
|
+
const n: i32 = toI32(bytes.length)
|
|
2254
|
+
let j: i32 = i + 2
|
|
2255
|
+
while (j < n && toI32(bytes[j]) >= 32 && toI32(bytes[j]) < 64) {
|
|
2256
|
+
j = j + 1
|
|
2257
|
+
}
|
|
2258
|
+
if (j >= n && !last) {
|
|
2259
|
+
return -1
|
|
2260
|
+
}
|
|
2261
|
+
const final: i32 = j < n ? toI32(bytes[j]) : 0
|
|
2262
|
+
if (!(final >= 64 && final < 127)) {
|
|
2263
|
+
events.push(termKeyEvent(KEY_ESCAPE, TERM_ESC, 0, ACTION_PRESS))
|
|
2264
|
+
return i + 1
|
|
2265
|
+
}
|
|
2266
|
+
// The Linux console's F1–F5, `ESC [ [ A` to `ESC [ [ E` (console_codes(4)).
|
|
2267
|
+
if (final === TERM_BRACKET && j === i + 2) {
|
|
2268
|
+
if (j + 1 >= n && !last) {
|
|
2269
|
+
return -1
|
|
2270
|
+
}
|
|
2271
|
+
const f: i32 = j + 1 < n ? toI32(bytes[j + 1]) : 0
|
|
2272
|
+
if (f >= 65 && f <= 69) {
|
|
2273
|
+
events.push(termKeyEvent(KEY_F1 + (f - 65), f, 0, ACTION_PRESS))
|
|
2274
|
+
return j + 2
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
let from: i32 = i + 2
|
|
2278
|
+
let prefix: i32 = 0
|
|
2279
|
+
if (from < j && toI32(bytes[from]) >= 60) {
|
|
2280
|
+
prefix = toI32(bytes[from])
|
|
2281
|
+
from = from + 1
|
|
2282
|
+
}
|
|
2283
|
+
let to: i32 = from
|
|
2284
|
+
while (to < j && termByte(bytes, to) >= TERM_DIGIT_0) {
|
|
2285
|
+
to = to + 1
|
|
2286
|
+
}
|
|
2287
|
+
termCsiEvent(d, bytes, from, to, prefix, to < j ? toI32(bytes[j - 1]) : 0, final, events)
|
|
2288
|
+
return j + 1
|
|
2289
|
+
}
|
|
2290
|
+
|
|
2291
|
+
/**
|
|
2292
|
+
* Decode the control string at `bytes[i]` — DCS `ESC P`, APC `ESC _`, OSC
|
|
2293
|
+
* `ESC ]`, PM `ESC ^` or SOS `ESC X` — into one `EVENT_REPLY`, and answer
|
|
2294
|
+
* where decoding resumes. It ends at ST, `ESC \`, or BEL (ECMA-48 § 8.3.143;
|
|
2295
|
+
* xterm ctlseqs, "Operating System Commands"). Unfinished at the end it is -1
|
|
2296
|
+
* unless `last`, when it is dropped; an ESC inside that starts no ST breaks
|
|
2297
|
+
* it off, and decoding resumes at that ESC. A DCS reads as a CSI does then
|
|
2298
|
+
* carries its data in `text`: XTVERSION `ESC P > | name ST`, DECRQSS
|
|
2299
|
+
* `ESC P 1 $ r … ST`. An APC's first byte is its `reply` (kitty graphics
|
|
2300
|
+
* `ESC _ G … ST`); an OSC's number is its one param and the rest its text.
|
|
2301
|
+
*/
|
|
2302
|
+
const termDecodeString = (bytes: u8[], i: i32, last: boolean, events: TermEvent[]): i32 => {
|
|
2303
|
+
const n: i32 = toI32(bytes.length)
|
|
2304
|
+
const introducer: i32 = toI32(bytes[i + 1])
|
|
2305
|
+
let end: i32 = i + 2
|
|
2306
|
+
while (end < n && toI32(bytes[end]) !== 7 && toI32(bytes[end]) !== TERM_ESC) {
|
|
2307
|
+
end = end + 1
|
|
2308
|
+
}
|
|
2309
|
+
if (end + (end < n && toI32(bytes[end]) === TERM_ESC ? 1 : 0) >= n) {
|
|
2310
|
+
return last ? n : -1
|
|
2311
|
+
}
|
|
2312
|
+
let resume: i32 = end + 1
|
|
2313
|
+
if (toI32(bytes[end]) === TERM_ESC) {
|
|
2314
|
+
if (toI32(bytes[end + 1]) !== 92) {
|
|
2315
|
+
return end
|
|
2316
|
+
}
|
|
2317
|
+
resume = end + 2
|
|
2318
|
+
}
|
|
2319
|
+
const start: i32 = i + 2
|
|
2320
|
+
if (introducer === 95) {
|
|
2321
|
+
events.push(
|
|
2322
|
+
termReplyEvent(
|
|
2323
|
+
introducer,
|
|
2324
|
+
0,
|
|
2325
|
+
[],
|
|
2326
|
+
0,
|
|
2327
|
+
start < end ? toI32(bytes[start]) : 0,
|
|
2328
|
+
termBytesRange(bytes, start + 1, end)
|
|
2329
|
+
)
|
|
2330
|
+
)
|
|
2331
|
+
return resume
|
|
2332
|
+
}
|
|
2333
|
+
if (introducer === 93) {
|
|
2334
|
+
let semi: i32 = start
|
|
2335
|
+
while (semi < end && toI32(bytes[semi]) !== TERM_SEMICOLON) {
|
|
2336
|
+
semi = semi + 1
|
|
2337
|
+
}
|
|
2338
|
+
events.push(
|
|
2339
|
+
termReplyEvent(
|
|
2340
|
+
introducer,
|
|
2341
|
+
0,
|
|
2342
|
+
termCsiParams(bytes, start, semi),
|
|
2343
|
+
0,
|
|
2344
|
+
0,
|
|
2345
|
+
termBytesRange(bytes, semi + 1, end)
|
|
2346
|
+
)
|
|
2347
|
+
)
|
|
2348
|
+
return resume
|
|
2349
|
+
}
|
|
2350
|
+
if (introducer !== 80) {
|
|
2351
|
+
events.push(termReplyEvent(introducer, 0, [], 0, 0, termBytesRange(bytes, start, end)))
|
|
2352
|
+
return resume
|
|
2353
|
+
}
|
|
2354
|
+
let from: i32 = start
|
|
2355
|
+
let prefix: i32 = 0
|
|
2356
|
+
if (from < end && toI32(bytes[from]) >= 60 && toI32(bytes[from]) < 64) {
|
|
2357
|
+
prefix = toI32(bytes[from])
|
|
2358
|
+
from = from + 1
|
|
2359
|
+
}
|
|
2360
|
+
let to: i32 = from
|
|
2361
|
+
while (to < end && toI32(bytes[to]) >= TERM_DIGIT_0 && toI32(bytes[to]) < 64) {
|
|
2362
|
+
to = to + 1
|
|
2363
|
+
}
|
|
2364
|
+
let f: i32 = to
|
|
2365
|
+
while (f < end && toI32(bytes[f]) >= 32 && toI32(bytes[f]) < TERM_DIGIT_0) {
|
|
2366
|
+
f = f + 1
|
|
2367
|
+
}
|
|
2368
|
+
const intermediate: i32 = f > to ? toI32(bytes[f - 1]) : 0
|
|
2369
|
+
const final: i32 = f < end ? toI32(bytes[f]) : 0
|
|
2370
|
+
events.push(
|
|
2371
|
+
termReplyEvent(
|
|
2372
|
+
introducer,
|
|
2373
|
+
prefix,
|
|
2374
|
+
termCsiParams(bytes, from, to),
|
|
2375
|
+
intermediate,
|
|
2376
|
+
final,
|
|
2377
|
+
termBytesRange(bytes, f + 1, end)
|
|
2378
|
+
)
|
|
2379
|
+
)
|
|
2380
|
+
return resume
|
|
2381
|
+
}
|
|
2382
|
+
|
|
2383
|
+
/** The byte at `k` of `bytes`, or -1 past either end. */
|
|
2384
|
+
const termByte = (bytes: u8[], k: i32): i32 => (k >= 0 && k < toI32(bytes.length) ? toI32(bytes[k]) : -1)
|
|
2385
|
+
|
|
2386
|
+
/** Whether `b`, after an ESC, opens a control string: DCS `P`, SOS `X`, OSC `]`, PM `^` or APC `_`. */
|
|
2387
|
+
const termOpensString = (b: i32): boolean => b === 80 || b === 88 || b === 93 || b === 94 || b === 95
|
|
2388
|
+
|
|
2389
|
+
/**
|
|
2390
|
+
* Decode `bytes` into `events` for `feedEvents` and `pollEvents`, and answer
|
|
2391
|
+
* how many bytes were used. Unless `last` is set, an unfinished sequence at
|
|
2392
|
+
* the end is left unused for the caller to hold; with it set, the unfinished
|
|
2393
|
+
* start is given up. A paste carries on across calls in `d`.
|
|
2394
|
+
*/
|
|
2395
|
+
const termDecodeEvents = (d: KeyDecoder, bytes: u8[], last: boolean, events: TermEvent[]): i32 => {
|
|
2396
|
+
const n: i32 = toI32(bytes.length)
|
|
2397
|
+
let i: i32 = 0
|
|
2398
|
+
while (i < toI32(bytes.length)) {
|
|
2399
|
+
if (!(i >= 0)) {
|
|
2400
|
+
panic("nish/term: the event decoder stepped back")
|
|
2401
|
+
}
|
|
2402
|
+
let next: i32 = i + 1
|
|
2403
|
+
const b: i32 = toI32(bytes[i])
|
|
2404
|
+
if (d.pasting) {
|
|
2405
|
+
next = termDecodePaste(d, bytes, i, last, events)
|
|
2406
|
+
if (next < n && d.pasting) {
|
|
2407
|
+
return next
|
|
2408
|
+
}
|
|
2409
|
+
} else if (b === TERM_ESC && i + 1 >= n) {
|
|
2410
|
+
if (!last) {
|
|
2411
|
+
return i
|
|
2412
|
+
}
|
|
2413
|
+
events.push(termKeyEvent(KEY_ESCAPE, b, 0, ACTION_PRESS))
|
|
2414
|
+
} else if (b === TERM_ESC) {
|
|
2415
|
+
const after: i32 = termByte(bytes, i + 1)
|
|
2416
|
+
if (after === TERM_BRACKET) {
|
|
2417
|
+
next = termDecodeCsi(d, bytes, i, last, events)
|
|
2418
|
+
} else if (termOpensString(after)) {
|
|
2419
|
+
next = termDecodeString(bytes, i, last, events)
|
|
2420
|
+
} else if (after === 79 && i + 2 >= n && !last) {
|
|
2421
|
+
next = -1
|
|
2422
|
+
} else if (after === 79 && termByte(bytes, i + 2) >= 64 && termByte(bytes, i + 2) < 127) {
|
|
2423
|
+
// SS3 `ESC O X`: the arrows, Home, End and F1–F4 in application
|
|
2424
|
+
// mode, and keypad Enter `ESC O M` (xterm ctlseqs, "PC-Style Function Keys").
|
|
2425
|
+
const f: i32 = termByte(bytes, i + 2)
|
|
2426
|
+
events.push(
|
|
2427
|
+
termKeyEvent(f === 77 ? KEY_ENTER : termLetterKind(f), f === 77 ? 13 : f, 0, ACTION_PRESS)
|
|
2428
|
+
)
|
|
2429
|
+
next = i + 3
|
|
2430
|
+
} else if (after !== TERM_ESC && after !== 79 && after < 128) {
|
|
2431
|
+
// Alt held with a key sends ESC before it (xterm's metaSendsEscape).
|
|
2432
|
+
events.push(termKeyEvent(termAsciiKind(after), after, MOD_ALT, ACTION_PRESS))
|
|
2433
|
+
next = i + 2
|
|
2434
|
+
} else {
|
|
2435
|
+
events.push(termKeyEvent(KEY_ESCAPE, b, 0, ACTION_PRESS))
|
|
2436
|
+
}
|
|
2437
|
+
if (next < 0) {
|
|
2438
|
+
return i
|
|
2439
|
+
}
|
|
2440
|
+
} else if (b < 128) {
|
|
2441
|
+
events.push(termKeyEvent(termAsciiKind(b), b, 0, ACTION_PRESS))
|
|
2442
|
+
} else {
|
|
2443
|
+
const char: i32 = termUtf8At(bytes, i, last)
|
|
2444
|
+
if (char < 0) {
|
|
2445
|
+
return i
|
|
2446
|
+
}
|
|
2447
|
+
events.push(termKeyEvent(KEY_CHAR, char >> 3, 0, ACTION_PRESS))
|
|
2448
|
+
next = i + (char & 7)
|
|
2449
|
+
}
|
|
2450
|
+
i = next
|
|
2451
|
+
}
|
|
2452
|
+
return n
|
|
2453
|
+
}
|