@amritk/nish-x86_64-linux 0.20.0 → 0.22.1

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.
Files changed (74) hide show
  1. package/INSTALL.md +52 -10
  2. package/bin/nish +0 -0
  3. package/docs/AI.md +2777 -0
  4. package/package.json +2 -1
  5. package/runtime/browser.d.ts +6 -0
  6. package/runtime/browser.mjs +77 -0
  7. package/runtime/core/arrays.d.ts +36 -0
  8. package/runtime/core/arrays.mjs +153 -0
  9. package/runtime/core/checks.mjs +20 -0
  10. package/runtime/core/host.d.ts +80 -0
  11. package/runtime/core/host.mjs +325 -0
  12. package/runtime/core/numbers.d.ts +66 -0
  13. package/runtime/core/numbers.mjs +343 -0
  14. package/runtime/core/result.d.ts +45 -0
  15. package/runtime/core/result.mjs +140 -0
  16. package/runtime/core/strings.d.ts +14 -0
  17. package/runtime/core/strings.mjs +169 -0
  18. package/runtime/core.d.ts +9 -0
  19. package/runtime/core.mjs +55 -0
  20. package/runtime/host.d.ts +6 -0
  21. package/runtime/nish.d.ts +46 -2
  22. package/runtime/nish.h +75 -2
  23. package/runtime/nish.mjs +23 -3
  24. package/runtime/node.mjs +386 -0
  25. package/runtime/runtime-bytes.c +401 -0
  26. package/runtime/runtime-cross.c +1 -1
  27. package/runtime/runtime-host.c +1 -1
  28. package/runtime/runtime-net.c +12 -1
  29. package/runtime/runtime-os.c +1 -1
  30. package/runtime/runtime-parallel.c +1 -1
  31. package/runtime/runtime-simd.c +231 -7
  32. package/runtime/runtime-term.c +504 -0
  33. package/runtime/runtime-wasm.c +0 -11
  34. package/runtime/runtime.c +190 -6
  35. package/runtime/shim.mjs +6 -985
  36. package/runtime/sqlite/LICENSE.md +85 -0
  37. package/runtime/sqlite/README.md +45 -0
  38. package/runtime/sqlite/nish-sqlite.c +359 -0
  39. package/runtime/sqlite/sqlite3.c +271131 -0
  40. package/runtime/sqlite/sqlite3.h +14447 -0
  41. package/scripts/build.sh +311 -59
  42. package/std/LICENSE-fast-float +23 -0
  43. package/std/LICENSE-fdlibm +8 -0
  44. package/std/README.md +32 -12
  45. package/std/base64.ts +116 -0
  46. package/std/bytes.ts +256 -0
  47. package/std/cli.ts +344 -0
  48. package/std/collections.ts +6 -5
  49. package/std/crypto/LICENSE-crypt_blowfish +26 -0
  50. package/std/crypto/base64url.ts +125 -0
  51. package/std/crypto/bcrypt.ts +1557 -0
  52. package/std/crypto/hmac.ts +138 -2
  53. package/std/crypto/x509.ts +11 -122
  54. package/std/json-tokenizer.ts +2998 -57
  55. package/std/json-value.ts +22 -3
  56. package/std/json.ts +87 -8
  57. package/std/math.ts +608 -0
  58. package/std/net/http1-server.ts +282 -40
  59. package/std/net/http1.ts +40 -21
  60. package/std/net/http3-server.ts +50 -12
  61. package/std/net/postgres.ts +2294 -0
  62. package/std/net/prefork.ts +23 -3
  63. package/std/net/quic-listener.ts +20 -4
  64. package/std/random.ts +66 -4
  65. package/std/regex.ts +4274 -0
  66. package/std/sqlite.ts +391 -0
  67. package/std/term-io.ts +978 -0
  68. package/std/term-pixels.ts +874 -0
  69. package/std/term-probe.ts +459 -0
  70. package/std/term-rate.ts +993 -0
  71. package/std/term.ts +2453 -0
  72. package/std/text.ts +90 -7
  73. package/std/yaml-lines.ts +141 -0
  74. 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
+ }