@amritk/nish-x86_64-linux 0.20.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INSTALL.md +22 -10
- package/bin/nish +0 -0
- package/docs/AI.md +2759 -0
- package/package.json +2 -1
- package/runtime/browser.d.ts +14 -0
- package/runtime/browser.mjs +88 -0
- package/runtime/core/arrays.d.ts +18 -0
- package/runtime/core/arrays.mjs +125 -0
- package/runtime/core/checks.mjs +20 -0
- package/runtime/core/host.d.ts +70 -0
- package/runtime/core/host.mjs +311 -0
- package/runtime/core/numbers.d.ts +53 -0
- package/runtime/core/numbers.mjs +316 -0
- package/runtime/core/result.d.ts +41 -0
- package/runtime/core/result.mjs +127 -0
- package/runtime/core/strings.d.ts +14 -0
- package/runtime/core/strings.mjs +169 -0
- package/runtime/core.d.ts +9 -0
- package/runtime/core.mjs +55 -0
- package/runtime/host.d.ts +6 -0
- package/runtime/nish.d.ts +44 -0
- package/runtime/nish.h +75 -2
- package/runtime/nish.mjs +23 -3
- package/runtime/node.mjs +383 -0
- package/runtime/runtime-bytes.c +401 -0
- package/runtime/runtime-cross.c +1 -1
- package/runtime/runtime-host.c +1 -1
- package/runtime/runtime-os.c +1 -1
- package/runtime/runtime-parallel.c +1 -1
- package/runtime/runtime-simd.c +231 -7
- package/runtime/runtime-term.c +504 -0
- package/runtime/runtime-wasm.c +0 -11
- package/runtime/runtime.c +190 -6
- package/runtime/shim.mjs +6 -985
- package/runtime/sqlite/LICENSE.md +85 -0
- package/runtime/sqlite/README.md +45 -0
- package/runtime/sqlite/nish-sqlite.c +359 -0
- package/runtime/sqlite/sqlite3.c +271131 -0
- package/runtime/sqlite/sqlite3.h +14447 -0
- package/scripts/build.sh +261 -53
- package/std/LICENSE-fast-float +23 -0
- package/std/LICENSE-fdlibm +8 -0
- package/std/README.md +31 -11
- package/std/base64.ts +116 -0
- package/std/bytes.ts +256 -0
- package/std/cli.ts +328 -0
- package/std/collections.ts +6 -5
- package/std/crypto/LICENSE-crypt_blowfish +26 -0
- package/std/crypto/base64url.ts +125 -0
- package/std/crypto/bcrypt.ts +760 -0
- package/std/crypto/hmac.ts +138 -2
- package/std/crypto/x509.ts +11 -122
- package/std/json-tokenizer.ts +2998 -57
- package/std/json-value.ts +22 -3
- package/std/json.ts +83 -1
- package/std/math.ts +608 -0
- package/std/net/http1-server.ts +85 -8
- package/std/net/http3-server.ts +50 -12
- package/std/net/postgres.ts +1875 -0
- package/std/net/quic-listener.ts +20 -4
- package/std/regex.ts +4274 -0
- package/std/sqlite.ts +391 -0
- package/std/term-io.ts +978 -0
- package/std/term-pixels.ts +874 -0
- package/std/term-probe.ts +459 -0
- package/std/term-rate.ts +993 -0
- package/std/term.ts +2453 -0
- package/std/text.ts +90 -7
- package/std/yaml-lines.ts +141 -0
- package/std/yaml.ts +4675 -0
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `std/term-probe` — what the terminal a program runs in can do (its colours,
|
|
3
|
+
* keyboard protocol, graphics and size), asked of it once at start and read
|
|
4
|
+
* from its answer.
|
|
5
|
+
*
|
|
6
|
+
* A program writes `probeQueries(env)` in one write, then feeds what the
|
|
7
|
+
* terminal sends back to a `TermProbe` until `done()` (the terminal answered
|
|
8
|
+
* the last query) or `timedOut(now)`, and reads the `TermCaps` from `result()`.
|
|
9
|
+
* The module is pure: the environment comes in as a `TermEnv`, times are the
|
|
10
|
+
* caller's milliseconds as `f64` from any origin, and nothing here reads,
|
|
11
|
+
* writes or waits.
|
|
12
|
+
*
|
|
13
|
+
* ```ts
|
|
14
|
+
* import { TermEnv, TermProbe, probeQueries } from "nish/term-probe";
|
|
15
|
+
*
|
|
16
|
+
* const env: TermEnv = new TermEnv();
|
|
17
|
+
* env.term = "xterm-256color";
|
|
18
|
+
* const probe: TermProbe = new TermProbe(env, 80, 24, 0.0);
|
|
19
|
+
* const queries: string = probeQueries(env); // write these to the terminal
|
|
20
|
+
* // …feed each read: probe.feed(bytes, count, nowMs), until probe.done() or probe.timedOut(nowMs)
|
|
21
|
+
* console.log(`${probe.result().colors}`);
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* - `TermEnv` — the environment variables the probe reads, each "" when
|
|
25
|
+
* unset, one field each in camelCase: `term` (`TERM`), `colorterm`
|
|
26
|
+
* (`COLORTERM`), `termProgram` (`TERM_PROGRAM`), `tmux` (`TMUX`), `sty`
|
|
27
|
+
* (`STY`), `sshConnection`, `sshClient` and `sshTty` (`SSH_CONNECTION`,
|
|
28
|
+
* `SSH_CLIENT`, `SSH_TTY`), `noColor` (`NO_COLOR`), `nishTermGraphics`
|
|
29
|
+
* (`NISH_TERM_GRAPHICS`) and `nishTermFps` (`NISH_TERM_FPS`, which the probe
|
|
30
|
+
* carries for the renderer and does not read).
|
|
31
|
+
* - `probeQueries(env)` — every query, in this order, as one string:
|
|
32
|
+
* XTVERSION `CSI > q`, DECRQM for synchronized output `CSI ? 2026 $ p` and
|
|
33
|
+
* pixel mouse `CSI ? 1016 $ p`, kitty's keyboard flags `CSI ? u`, a kitty
|
|
34
|
+
* graphics query `APC G i=31,s=1,v=1,a=q,t=d,f=24;AAAA ST`, XTSMGRAPHICS
|
|
35
|
+
* for the sixel colour registers `CSI ? 1 ; 1 ; 0 S`, the cell size
|
|
36
|
+
* `CSI 16 t`, the text area size `CSI 14 t`, a true-colour background set
|
|
37
|
+
* and read back with DECRQSS (`CSI 48 ; 2 ; 1 ; 2 ; 3 m`, `DCS $ q m ST`,
|
|
38
|
+
* `CSI m`), and DA1 `CSI c` last. Terminals answer in order, so the answer
|
|
39
|
+
* to DA1, which every terminal gives, means the rest are in. Under tmux
|
|
40
|
+
* (`TMUX` set) the queries only the outer terminal can answer — kitty
|
|
41
|
+
* graphics and the two sizes — go inside tmux's passthrough,
|
|
42
|
+
* `DCS tmux; … ST` with each ESC doubled; under GNU screen (`STY` set) the
|
|
43
|
+
* sizes go inside screen's `DCS … ST`, and the kitty graphics query and
|
|
44
|
+
* DECRQSS, which hold an ST of their own and which screen would forward
|
|
45
|
+
* rather than answer, are left out. On the Linux console (`TERM=linux`)
|
|
46
|
+
* both are left out too: it has no string states and would print them. DA1 is never wrapped: the multiplexer
|
|
47
|
+
* answers it, so a multiplexer that forwards no reply still ends the probe.
|
|
48
|
+
* - `TermProbe` — the probe in progress, made when the queries are sent.
|
|
49
|
+
* `feed(bytes, count, nowMs)` decodes a read through its own `KeyDecoder`
|
|
50
|
+
* (`nish/term`), keeping the replies and holding back every other event (a
|
|
51
|
+
* key typed while the probe ran) for `heldEvents()`, in order. `done()` is
|
|
52
|
+
* true once DA1 is answered; `timedOut(nowMs)` once `timeoutMs` (250 by
|
|
53
|
+
* default) has passed since the send with no answer to DA1. Either way
|
|
54
|
+
* the program then calls `finish(nowMs)` once: it gives up whatever the
|
|
55
|
+
* decoder still holds (a key cut off at the end of the read with DA1, say)
|
|
56
|
+
* and returns `heldEvents()` complete, and later reads go to the program's
|
|
57
|
+
* own decoder. `rttMs()` is the time from the
|
|
58
|
+
* send to the answer to DA1, -1 before it. `result()` reads the replies and
|
|
59
|
+
* the environment into a `TermCaps`, and can be called at any point.
|
|
60
|
+
* - `TermCaps` — what the terminal can do: `colors` (16, 256 or 16777216),
|
|
61
|
+
* `trueColor`, `sync`, `kittyKeyboard`, `kittyGraphics`, `sixel`,
|
|
62
|
+
* `sixelRegisters`, `cellWidthPx` and `cellHeightPx`, `pixelMouse`,
|
|
63
|
+
* `multiplexer` ("none", "tmux" or "screen"), `linuxConsole`, `remoteHint`,
|
|
64
|
+
* `answered`, `probeRttMs`, `graphics` ("kitty", "sixel", "halfblock" or
|
|
65
|
+
* "ascii"), `name` and `overrides`; each field's comment says where it
|
|
66
|
+
* comes from.
|
|
67
|
+
*
|
|
68
|
+
* The environment decides where the terminal cannot answer, and where the
|
|
69
|
+
* user says so: `COLORTERM=truecolor` or `24bit` means true colour, as does
|
|
70
|
+
* a DECRQSS that reads the 24-bit background back; otherwise a `TERM` with
|
|
71
|
+
* `256color` in it means 256 colours, and anything else 16. `TERM=linux` is
|
|
72
|
+
* the Linux console: 16 colours, ASCII graphics, no kitty keyboard and no
|
|
73
|
+
* synchronized output, whatever it answered. `NO_COLOR` set to anything
|
|
74
|
+
* (no-color.org) means 16 colours and touches nothing but colour.
|
|
75
|
+
* `NISH_TERM_GRAPHICS` names a graphics backend to use instead of the one the
|
|
76
|
+
* answers pick; a pixel backend the terminal did not answer for is refused
|
|
77
|
+
* when it would come out as garbage or nothing — on the Linux console, under
|
|
78
|
+
* a multiplexer, or when the terminal answered nothing at all. `overrides`
|
|
79
|
+
* says what each variable did, refusals included. The match is exact:
|
|
80
|
+
* `kitty`, `sixel`, `halfblock` or `ascii`, lower case, nothing around it.
|
|
81
|
+
*
|
|
82
|
+
* Under a multiplexer the probe sees the multiplexer first. tmux and screen
|
|
83
|
+
* answer DA1 themselves, so `done()` comes on their answer, before any reply
|
|
84
|
+
* forwarded through the passthrough; `probeRttMs` is the round trip to the
|
|
85
|
+
* multiplexer, and graphics come from what the multiplexer itself answers
|
|
86
|
+
* (tmux's own sixel, say). The environment is taken at face value: an
|
|
87
|
+
* `SSH_*` variable or `COLORTERM` left in a tmux session by the client that
|
|
88
|
+
* started it still counts, though the client attached now may differ.
|
|
89
|
+
*/
|
|
90
|
+
import { EVENT_REPLY, KeyDecoder, TermEvent } from "nish/term"
|
|
91
|
+
import { replaceAll } from "nish/text"
|
|
92
|
+
|
|
93
|
+
/** How long a probe waits for the answer to DA1 by default, in milliseconds. */
|
|
94
|
+
export const TERM_PROBE_TIMEOUT_MS: f64 = 250.0
|
|
95
|
+
|
|
96
|
+
/** How long the probe's decoder holds an unfinished sequence, in milliseconds. */
|
|
97
|
+
const TERM_PROBE_ESCAPE_MS: f64 = 50.0
|
|
98
|
+
|
|
99
|
+
/** The colour count of a terminal with 24-bit colour. */
|
|
100
|
+
const TERM_PROBE_TRUE_COLORS: i32 = 16777216
|
|
101
|
+
|
|
102
|
+
/** The environment the probe reads, each variable "" when unset. */
|
|
103
|
+
export class TermEnv {
|
|
104
|
+
/** `TERM`: the terminal type, such as `xterm-256color` or `linux`. */
|
|
105
|
+
term: string
|
|
106
|
+
/** `COLORTERM`: `truecolor` or `24bit` where the terminal has 24-bit colour. */
|
|
107
|
+
colorterm: string
|
|
108
|
+
/** `TERM_PROGRAM`: the terminal's name where it sets one, such as `iTerm.app` or `Apple_Terminal`. */
|
|
109
|
+
termProgram: string
|
|
110
|
+
/** `TMUX`: set inside tmux. */
|
|
111
|
+
tmux: string
|
|
112
|
+
/** `STY`: set inside GNU screen. */
|
|
113
|
+
sty: string
|
|
114
|
+
/** `SSH_CONNECTION`: set by sshd for a remote session. */
|
|
115
|
+
sshConnection: string
|
|
116
|
+
/** `SSH_CLIENT`: set by sshd for a remote session. */
|
|
117
|
+
sshClient: string
|
|
118
|
+
/** `SSH_TTY`: set by sshd for a remote session with a terminal. */
|
|
119
|
+
sshTty: string
|
|
120
|
+
/** `NO_COLOR`: set to anything to ask for no colour (no-color.org). */
|
|
121
|
+
noColor: string
|
|
122
|
+
/** `NISH_TERM_GRAPHICS`: a graphics backend to use instead of the probed one, `kitty`, `sixel`, `halfblock` or `ascii`. */
|
|
123
|
+
nishTermGraphics: string
|
|
124
|
+
/** `NISH_TERM_FPS`: a frame rate for the renderer; carried, not read, by the probe. */
|
|
125
|
+
nishTermFps: string
|
|
126
|
+
|
|
127
|
+
/** An environment with every variable unset. */
|
|
128
|
+
constructor() {
|
|
129
|
+
this.term = ""
|
|
130
|
+
this.colorterm = ""
|
|
131
|
+
this.termProgram = ""
|
|
132
|
+
this.tmux = ""
|
|
133
|
+
this.sty = ""
|
|
134
|
+
this.sshConnection = ""
|
|
135
|
+
this.sshClient = ""
|
|
136
|
+
this.sshTty = ""
|
|
137
|
+
this.noColor = ""
|
|
138
|
+
this.nishTermGraphics = ""
|
|
139
|
+
this.nishTermFps = ""
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** What a terminal can do, from its answers to `probeQueries` and the environment. */
|
|
144
|
+
export class TermCaps {
|
|
145
|
+
/** The multiplexer in between: "none", "tmux" (`TMUX` set) or "screen" (`STY` set). */
|
|
146
|
+
multiplexer: string
|
|
147
|
+
/** The time from the send to the answer to DA1, in milliseconds; -1 unanswered. */
|
|
148
|
+
probeRttMs: f64
|
|
149
|
+
/** The graphics backend to draw with, best first — "kitty", "sixel" (256 registers or more), "halfblock", "ascii" — after `NISH_TERM_GRAPHICS`. */
|
|
150
|
+
graphics: string
|
|
151
|
+
/** The terminal's name and version from XTVERSION, else `TERM_PROGRAM`, else "". */
|
|
152
|
+
name: string
|
|
153
|
+
/** What the environment forced, one `VARIABLE: effect` per entry joined by `; `; "" when nothing. `nish/term-io` adds `NISH_TERM_FPS`'s, which the controller applies. */
|
|
154
|
+
overrides: string
|
|
155
|
+
/** How many colours to draw with: 16, 256 or 16777216. */
|
|
156
|
+
colors: i32
|
|
157
|
+
/** How many sixel colour registers there are, from XTSMGRAPHICS; 0 unanswered. */
|
|
158
|
+
sixelRegisters: i32
|
|
159
|
+
/** A cell's width in pixels: `CSI 16 t`, else the text area's from `CSI 14 t` over the columns; 0 unknown. */
|
|
160
|
+
cellWidthPx: i32
|
|
161
|
+
/** A cell's height in pixels: `CSI 16 t`, else the text area's from `CSI 14 t` over the rows; 0 unknown. */
|
|
162
|
+
cellHeightPx: i32
|
|
163
|
+
/** Whether 24-bit colour works: `COLORTERM`, or the background DECRQSS read back. */
|
|
164
|
+
trueColor: boolean
|
|
165
|
+
/** Whether synchronized output (mode 2026) is known: DECRPM answered set or reset. */
|
|
166
|
+
sync: boolean
|
|
167
|
+
/** Whether the kitty keyboard protocol is there: `CSI ? flags u` answered. */
|
|
168
|
+
kittyKeyboard: boolean
|
|
169
|
+
/** Whether kitty graphics are there: the query answered `OK`. */
|
|
170
|
+
kittyGraphics: boolean
|
|
171
|
+
/** Whether sixel is there: DA1 lists 4. */
|
|
172
|
+
sixel: boolean
|
|
173
|
+
/** Whether SGR mouse reports in pixels (mode 1016) are known: DECRPM answered set or reset. */
|
|
174
|
+
pixelMouse: boolean
|
|
175
|
+
/** Whether this is the Linux console (`TERM=linux`). */
|
|
176
|
+
linuxConsole: boolean
|
|
177
|
+
/** Whether the session looks remote (`SSH_CONNECTION`, `SSH_CLIENT` or `SSH_TTY` set), so slow. */
|
|
178
|
+
remoteHint: boolean
|
|
179
|
+
/** Whether the terminal answered DA1; false when the probe timed out. */
|
|
180
|
+
answered: boolean
|
|
181
|
+
|
|
182
|
+
/** Caps of a terminal that answered nothing: 16 colours, ASCII. */
|
|
183
|
+
constructor() {
|
|
184
|
+
this.colors = 16
|
|
185
|
+
this.trueColor = false
|
|
186
|
+
this.sync = false
|
|
187
|
+
this.kittyKeyboard = false
|
|
188
|
+
this.kittyGraphics = false
|
|
189
|
+
this.sixel = false
|
|
190
|
+
this.sixelRegisters = 0
|
|
191
|
+
this.cellWidthPx = 0
|
|
192
|
+
this.cellHeightPx = 0
|
|
193
|
+
this.pixelMouse = false
|
|
194
|
+
this.multiplexer = "none"
|
|
195
|
+
this.linuxConsole = false
|
|
196
|
+
this.remoteHint = false
|
|
197
|
+
this.answered = false
|
|
198
|
+
this.probeRttMs = -1.0
|
|
199
|
+
this.graphics = "ascii"
|
|
200
|
+
this.name = ""
|
|
201
|
+
this.overrides = ""
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** The multiplexer `env` says the program runs under: "tmux", "screen" or "none". */
|
|
206
|
+
const termMultiplexer = (env: TermEnv): string => {
|
|
207
|
+
if (env.tmux !== "") {
|
|
208
|
+
return "tmux"
|
|
209
|
+
}
|
|
210
|
+
return env.sty !== "" ? "screen" : "none"
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** `query` inside the passthrough of `multiplexer`, so the outer terminal gets it: tmux's with each ESC doubled, screen's as it is. */
|
|
214
|
+
const termPassthrough = (multiplexer: string, query: string): string => {
|
|
215
|
+
if (multiplexer === "tmux") {
|
|
216
|
+
return `\x1bPtmux;${replaceAll(query, "\x1b", "\x1b\x1b")}\x1b\\`
|
|
217
|
+
}
|
|
218
|
+
return multiplexer === "screen" ? `\x1bP${query}\x1b\\` : query
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** Every query the probe asks, as one write; `TermProbe` reads the answers. */
|
|
222
|
+
export const probeQueries = (env: TermEnv): string => {
|
|
223
|
+
const mux: string = termMultiplexer(env)
|
|
224
|
+
const parts: string[] = ["\x1b[>q", "\x1b[?2026$p", "\x1b[?1016$p", "\x1b[?u"]
|
|
225
|
+
const strings: boolean = mux !== "screen" && env.term !== "linux"
|
|
226
|
+
if (strings) {
|
|
227
|
+
parts.push(termPassthrough(mux, "\x1b_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\x1b\\"))
|
|
228
|
+
}
|
|
229
|
+
parts.push("\x1b[?1;1;0S")
|
|
230
|
+
parts.push(termPassthrough(mux, "\x1b[16t"))
|
|
231
|
+
parts.push(termPassthrough(mux, "\x1b[14t"))
|
|
232
|
+
if (strings) {
|
|
233
|
+
parts.push("\x1b[48;2;1;2;3m\x1bP$qm\x1b\\\x1b[m")
|
|
234
|
+
}
|
|
235
|
+
parts.push("\x1b[c")
|
|
236
|
+
return parts.join("")
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** Whether `e` is the reply opened by `introducer`, with private marker `prefix`, intermediate `intermediate` and final `final`. */
|
|
240
|
+
const termIsReply = (e: TermEvent, introducer: i32, prefix: i32, intermediate: i32, final: i32): boolean =>
|
|
241
|
+
e.type === EVENT_REPLY &&
|
|
242
|
+
e.introducer === introducer &&
|
|
243
|
+
e.prefix === prefix &&
|
|
244
|
+
e.intermediate === intermediate &&
|
|
245
|
+
e.reply === final
|
|
246
|
+
|
|
247
|
+
/** Parameter `k` of `e`, or -1 when it has fewer. */
|
|
248
|
+
const termParam = (e: TermEvent, k: i32): i32 => (k < toI32(e.params.length) ? e.params[k] : -1)
|
|
249
|
+
|
|
250
|
+
/** Whether a DECRPM value says the mode is known: set (1), reset (2) or permanently set (3). */
|
|
251
|
+
const termModeKnown = (value: i32): boolean => value >= 1 && value <= 3
|
|
252
|
+
|
|
253
|
+
/** Whether `e` has a parameter equal to `value`. */
|
|
254
|
+
const termHasParam = (e: TermEvent, value: i32): boolean => {
|
|
255
|
+
for (const p of e.params) {
|
|
256
|
+
if (p === value) {
|
|
257
|
+
return true
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return false
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* A probe in progress: made when `probeQueries` is written, at `sentAtMs`,
|
|
265
|
+
* for a window of `cols` by `rows` cells, fed what the terminal sends.
|
|
266
|
+
*/
|
|
267
|
+
export class TermProbe {
|
|
268
|
+
/** The environment the queries were made for. */
|
|
269
|
+
env: TermEnv
|
|
270
|
+
/** The window's width in cells, to turn `CSI 14 t` into a cell width. */
|
|
271
|
+
cols: i32
|
|
272
|
+
/** The window's height in cells, to turn `CSI 14 t` into a cell height. */
|
|
273
|
+
rows: i32
|
|
274
|
+
/** When the queries were written. */
|
|
275
|
+
sentAtMs: f64
|
|
276
|
+
/** How long to wait for the answer to DA1 (`TERM_PROBE_TIMEOUT_MS` by default). */
|
|
277
|
+
timeoutMs: f64
|
|
278
|
+
/** The decoder the terminal's bytes go through. */
|
|
279
|
+
decoder: KeyDecoder
|
|
280
|
+
/** The replies so far, in order. */
|
|
281
|
+
replies: TermEvent[]
|
|
282
|
+
/** Every other event so far, in order: what the user typed during the probe. */
|
|
283
|
+
held: TermEvent[]
|
|
284
|
+
/** When DA1 was answered; -1 before. */
|
|
285
|
+
answeredAtMs: f64
|
|
286
|
+
|
|
287
|
+
/** A probe whose queries were written at `sentAtMs` to a window of `cols` by `rows` cells. */
|
|
288
|
+
constructor(env: TermEnv, cols: i32, rows: i32, sentAtMs: f64) {
|
|
289
|
+
this.env = env
|
|
290
|
+
this.cols = cols
|
|
291
|
+
this.rows = rows
|
|
292
|
+
this.sentAtMs = sentAtMs
|
|
293
|
+
this.timeoutMs = TERM_PROBE_TIMEOUT_MS
|
|
294
|
+
this.decoder = new KeyDecoder(TERM_PROBE_ESCAPE_MS)
|
|
295
|
+
this.replies = []
|
|
296
|
+
this.held = []
|
|
297
|
+
this.answeredAtMs = -1.0
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** Take in `events`: replies kept, DA1 marking the probe done, the rest held. */
|
|
301
|
+
take(events: TermEvent[], nowMs: f64): void {
|
|
302
|
+
for (const e of events) {
|
|
303
|
+
if (e.type !== EVENT_REPLY) {
|
|
304
|
+
this.held.push(e)
|
|
305
|
+
continue
|
|
306
|
+
}
|
|
307
|
+
this.replies.push(e)
|
|
308
|
+
if (this.answeredAtMs < 0.0 && termIsReply(e, 91, 63, 0, 99)) {
|
|
309
|
+
this.answeredAtMs = nowMs
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** Decode `bytes[0, count)`, read at `nowMs`. */
|
|
315
|
+
feed(bytes: u8[], count: i32, nowMs: f64): void {
|
|
316
|
+
this.take(this.decoder.feedEvents(bytes, count, nowMs), nowMs)
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/** Whether DA1 has been answered, so every reply is in. */
|
|
320
|
+
done(): boolean {
|
|
321
|
+
return this.answeredAtMs >= 0.0
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Whether `timeoutMs` has passed since the send with DA1 unanswered. Once it
|
|
326
|
+
* has, it calls `finish(nowMs)`, so `heldEvents` is complete.
|
|
327
|
+
*/
|
|
328
|
+
timedOut(nowMs: f64): boolean {
|
|
329
|
+
if (this.done() || nowMs - this.sentAtMs < this.timeoutMs) {
|
|
330
|
+
return false
|
|
331
|
+
}
|
|
332
|
+
this.finish(nowMs)
|
|
333
|
+
return true
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* End the probe at `nowMs`: what the decoder still holds — an ESC or the
|
|
338
|
+
* start of a key that came in the read with DA1, or an unfinished reply on a
|
|
339
|
+
* timeout — is given up (the ESC as a key, the reply as nothing) and
|
|
340
|
+
* `heldEvents` returned complete. Call it once, when `done()` or `timedOut`
|
|
341
|
+
* ends the probe, and hand every later read to the program's own decoder.
|
|
342
|
+
*/
|
|
343
|
+
finish(nowMs: f64): TermEvent[] {
|
|
344
|
+
this.take(this.decoder.pollEvents(nowMs + this.decoder.escapeTimeoutMs), nowMs)
|
|
345
|
+
return this.held
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/** The events that were not replies — keys typed, a paste — in the order they came, for the program to handle. */
|
|
349
|
+
heldEvents(): TermEvent[] {
|
|
350
|
+
return this.held
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/** The time from the send to the answer to DA1, in milliseconds; -1 before it. */
|
|
354
|
+
rttMs(): f64 {
|
|
355
|
+
return this.done() ? this.answeredAtMs - this.sentAtMs : -1.0
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/** What the terminal can do, from the replies so far and the environment. */
|
|
359
|
+
result(): TermCaps {
|
|
360
|
+
const env: TermEnv = this.env
|
|
361
|
+
const caps: TermCaps = new TermCaps()
|
|
362
|
+
const overrides: string[] = []
|
|
363
|
+
let sgrRead: boolean = false
|
|
364
|
+
let areaW: i32 = 0
|
|
365
|
+
let areaH: i32 = 0
|
|
366
|
+
for (const e of this.replies) {
|
|
367
|
+
if (termIsReply(e, 91, 63, 0, 99)) {
|
|
368
|
+
caps.answered = true
|
|
369
|
+
caps.sixel = caps.sixel || termHasParam(e, 4)
|
|
370
|
+
} else if (termIsReply(e, 80, 62, 0, 124)) {
|
|
371
|
+
caps.name = e.text
|
|
372
|
+
} else if (termIsReply(e, 91, 63, 36, 121) && termParam(e, 0) === 2026) {
|
|
373
|
+
caps.sync = termModeKnown(termParam(e, 1))
|
|
374
|
+
} else if (termIsReply(e, 91, 63, 36, 121) && termParam(e, 0) === 1016) {
|
|
375
|
+
caps.pixelMouse = termModeKnown(termParam(e, 1))
|
|
376
|
+
} else if (termIsReply(e, 91, 63, 0, 117)) {
|
|
377
|
+
caps.kittyKeyboard = true
|
|
378
|
+
} else if (termIsReply(e, 95, 0, 0, 71)) {
|
|
379
|
+
caps.kittyGraphics =
|
|
380
|
+
caps.kittyGraphics || (toI32(e.text.indexOf("i=31")) === 0 && toI32(e.text.indexOf(";OK")) >= 0)
|
|
381
|
+
} else if (termIsReply(e, 91, 63, 0, 83) && termParam(e, 0) === 1 && termParam(e, 1) === 0) {
|
|
382
|
+
caps.sixelRegisters = termParam(e, 2)
|
|
383
|
+
} else if (termIsReply(e, 91, 0, 0, 116) && termParam(e, 0) === 6) {
|
|
384
|
+
caps.cellHeightPx = termParam(e, 1)
|
|
385
|
+
caps.cellWidthPx = termParam(e, 2)
|
|
386
|
+
} else if (termIsReply(e, 91, 0, 0, 116) && termParam(e, 0) === 4) {
|
|
387
|
+
areaH = termParam(e, 1)
|
|
388
|
+
areaW = termParam(e, 2)
|
|
389
|
+
} else if (termIsReply(e, 80, 0, 36, 114) && termParam(e, 0) === 1) {
|
|
390
|
+
sgrRead = sgrRead || toI32(e.text.indexOf("48:2:")) >= 0 || toI32(e.text.indexOf("48;2;")) >= 0
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
if (caps.cellWidthPx <= 0 && areaW > 0 && this.cols > 0 && areaH > 0 && this.rows > 0) {
|
|
394
|
+
caps.cellWidthPx = (areaW / this.cols) | 0
|
|
395
|
+
caps.cellHeightPx = (areaH / this.rows) | 0
|
|
396
|
+
}
|
|
397
|
+
if (caps.cellWidthPx <= 0 || caps.cellHeightPx <= 0) {
|
|
398
|
+
caps.cellWidthPx = 0
|
|
399
|
+
caps.cellHeightPx = 0
|
|
400
|
+
}
|
|
401
|
+
if (caps.name === "") {
|
|
402
|
+
caps.name = env.termProgram
|
|
403
|
+
}
|
|
404
|
+
caps.multiplexer = termMultiplexer(env)
|
|
405
|
+
caps.linuxConsole = env.term === "linux"
|
|
406
|
+
caps.remoteHint = env.sshConnection !== "" || env.sshClient !== "" || env.sshTty !== ""
|
|
407
|
+
caps.probeRttMs = this.rttMs()
|
|
408
|
+
|
|
409
|
+
const colorterm: boolean = env.colorterm === "truecolor" || env.colorterm === "24bit"
|
|
410
|
+
caps.trueColor = colorterm || sgrRead
|
|
411
|
+
if (colorterm && !sgrRead) {
|
|
412
|
+
overrides.push(`COLORTERM=${env.colorterm}: true colour`)
|
|
413
|
+
}
|
|
414
|
+
if (caps.trueColor) {
|
|
415
|
+
caps.colors = TERM_PROBE_TRUE_COLORS
|
|
416
|
+
} else if (toI32(env.term.indexOf("256color")) >= 0) {
|
|
417
|
+
caps.colors = 256
|
|
418
|
+
}
|
|
419
|
+
if (caps.linuxConsole) {
|
|
420
|
+
caps.colors = 16
|
|
421
|
+
caps.trueColor = false
|
|
422
|
+
caps.kittyKeyboard = false
|
|
423
|
+
caps.sync = false
|
|
424
|
+
overrides.push("TERM=linux: 16 colours, ascii, no kitty keyboard, no sync")
|
|
425
|
+
}
|
|
426
|
+
if (env.noColor !== "") {
|
|
427
|
+
caps.colors = 16
|
|
428
|
+
caps.trueColor = false
|
|
429
|
+
overrides.push("NO_COLOR: 16 colours")
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (caps.linuxConsole) {
|
|
433
|
+
caps.graphics = "ascii"
|
|
434
|
+
} else if (caps.kittyGraphics) {
|
|
435
|
+
caps.graphics = "kitty"
|
|
436
|
+
} else if (caps.sixel && caps.sixelRegisters >= 256) {
|
|
437
|
+
caps.graphics = "sixel"
|
|
438
|
+
} else if (env.term !== "dumb") {
|
|
439
|
+
caps.graphics = "halfblock"
|
|
440
|
+
}
|
|
441
|
+
const forced: string = env.nishTermGraphics
|
|
442
|
+
if (forced === "halfblock" || forced === "ascii") {
|
|
443
|
+
caps.graphics = forced
|
|
444
|
+
overrides.push(`NISH_TERM_GRAPHICS=${forced}: ${forced}`)
|
|
445
|
+
} else if (forced === "kitty" || forced === "sixel") {
|
|
446
|
+
const answeredFor: boolean = forced === "kitty" ? caps.kittyGraphics : caps.sixel
|
|
447
|
+
if (answeredFor || (caps.answered && !caps.linuxConsole && caps.multiplexer === "none")) {
|
|
448
|
+
caps.graphics = forced
|
|
449
|
+
overrides.push(`NISH_TERM_GRAPHICS=${forced}: ${forced}`)
|
|
450
|
+
} else {
|
|
451
|
+
overrides.push(`NISH_TERM_GRAPHICS=${forced}: refused, the terminal did not answer for it`)
|
|
452
|
+
}
|
|
453
|
+
} else if (forced !== "") {
|
|
454
|
+
overrides.push(`NISH_TERM_GRAPHICS=${forced}: ignored, not kitty, sixel, halfblock or ascii`)
|
|
455
|
+
}
|
|
456
|
+
caps.overrides = overrides.join("; ")
|
|
457
|
+
return caps
|
|
458
|
+
}
|
|
459
|
+
}
|