@dkkoval/tui-preview 0.2.0 → 0.2.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.
- package/dist/TuiPreview.js +49 -34
- package/dist/{ghostty-vt.wasm → core/ghostty-vt.wasm} +0 -0
- package/dist/core/libghostty.js +1 -1
- package/dist/core/wasi.d.ts +7 -1
- package/dist/core/wasi.js +116 -20
- package/dist/index.js +2 -749
- package/package.json +7 -8
- package/dist/index.cjs +0 -2
package/dist/TuiPreview.js
CHANGED
|
@@ -36,9 +36,6 @@ export function TuiPreview(props) {
|
|
|
36
36
|
if (rect.width > 0 && rect.height > 0) {
|
|
37
37
|
updateFromPixels(rect.width, rect.height);
|
|
38
38
|
}
|
|
39
|
-
// Current fit strategy recalculates cols/rows from measured cell size and
|
|
40
|
-
// recreates the surface on size changes. This is acceptable for now, but
|
|
41
|
-
// an in-place resize path would be smoother for frequent container resizes.
|
|
42
39
|
const observer = new ResizeObserver(([entry]) => {
|
|
43
40
|
const { width, height } = entry.contentRect;
|
|
44
41
|
if (width > 0 && height > 0) {
|
|
@@ -53,6 +50,7 @@ export function TuiPreview(props) {
|
|
|
53
50
|
return;
|
|
54
51
|
let cancelled = false;
|
|
55
52
|
let disposeRenderSurface = null;
|
|
53
|
+
let activeBridge = null;
|
|
56
54
|
const container = containerRef.current;
|
|
57
55
|
const activeSize = termSize;
|
|
58
56
|
const setStatusAndNotify = (next) => {
|
|
@@ -70,7 +68,42 @@ export function TuiPreview(props) {
|
|
|
70
68
|
try {
|
|
71
69
|
let appCols = activeSize.cols;
|
|
72
70
|
let appRows = activeSize.rows;
|
|
73
|
-
|
|
71
|
+
const runOnce = async (surface) => {
|
|
72
|
+
const resolvedArgs = resolved.resolveArgv({ cols: appCols, rows: appRows });
|
|
73
|
+
const stdoutDecoder = new TextDecoder();
|
|
74
|
+
const stderrDecoder = new TextDecoder();
|
|
75
|
+
const flushSurfaceOutput = (data, decoder, bridge) => {
|
|
76
|
+
const decoded = decoder.decode(data, { stream: true });
|
|
77
|
+
if (decoded) {
|
|
78
|
+
surface.write(decoded);
|
|
79
|
+
}
|
|
80
|
+
for (const response of surface.drainResponses()) {
|
|
81
|
+
bridge.pushInput(response);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
let bridge;
|
|
85
|
+
bridge = new WasiBridge({
|
|
86
|
+
args: [resolved.wasm.toString(), ...resolvedArgs],
|
|
87
|
+
env: {
|
|
88
|
+
COLUMNS: String(appCols),
|
|
89
|
+
LINES: String(appRows),
|
|
90
|
+
...resolved.env,
|
|
91
|
+
},
|
|
92
|
+
stdout: (data) => flushSurfaceOutput(data, stdoutDecoder, bridge),
|
|
93
|
+
stderr: (data) => flushSurfaceOutput(data, stderrDecoder, bridge),
|
|
94
|
+
onExit: (code) => {
|
|
95
|
+
if (!cancelled) {
|
|
96
|
+
setStatusAndNotify("exited");
|
|
97
|
+
resolved.onExit?.(code);
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
activeBridge = bridge;
|
|
102
|
+
const wasmApp = await instantiateApp(resolved.wasm, bridge);
|
|
103
|
+
if (cancelled)
|
|
104
|
+
return;
|
|
105
|
+
await wasmApp.run();
|
|
106
|
+
};
|
|
74
107
|
const surface = await createMiniTerminalSurface({
|
|
75
108
|
container,
|
|
76
109
|
cols: activeSize.cols,
|
|
@@ -82,7 +115,9 @@ export function TuiPreview(props) {
|
|
|
82
115
|
interactive: resolved.mode !== "static" && resolved.interactive,
|
|
83
116
|
showCursor: resolved.mode !== "static",
|
|
84
117
|
wasmUrl: resolved.terminal.wasmUrl,
|
|
85
|
-
onInput: (data) =>
|
|
118
|
+
onInput: (data) => {
|
|
119
|
+
activeBridge?.pushInput(data);
|
|
120
|
+
},
|
|
86
121
|
});
|
|
87
122
|
if (cancelled) {
|
|
88
123
|
surface.dispose();
|
|
@@ -92,38 +127,11 @@ export function TuiPreview(props) {
|
|
|
92
127
|
appCols = surface.cols;
|
|
93
128
|
appRows = surface.rows;
|
|
94
129
|
cellSizeRef.current = surface.cellSize;
|
|
95
|
-
const resolvedArgs = resolved.resolveArgv({ cols: appCols, rows: appRows });
|
|
96
|
-
const stdoutDecoder = new TextDecoder();
|
|
97
|
-
const stderrDecoder = new TextDecoder();
|
|
98
|
-
const flushSurfaceOutput = (data, decoder) => {
|
|
99
|
-
const decoded = decoder.decode(data, { stream: true });
|
|
100
|
-
if (decoded) {
|
|
101
|
-
surface.write(decoded);
|
|
102
|
-
}
|
|
103
|
-
for (const response of surface.drainResponses()) {
|
|
104
|
-
bridge?.pushInput(response);
|
|
105
|
-
}
|
|
106
|
-
};
|
|
107
|
-
bridge = new WasiBridge({
|
|
108
|
-
args: [resolved.wasm.toString(), ...resolvedArgs],
|
|
109
|
-
env: resolved.env,
|
|
110
|
-
stdout: (data) => flushSurfaceOutput(data, stdoutDecoder),
|
|
111
|
-
stderr: (data) => flushSurfaceOutput(data, stderrDecoder),
|
|
112
|
-
onExit: (code) => {
|
|
113
|
-
if (!cancelled) {
|
|
114
|
-
setStatusAndNotify("exited");
|
|
115
|
-
resolved.onExit?.(code);
|
|
116
|
-
}
|
|
117
|
-
},
|
|
118
|
-
});
|
|
119
|
-
const wasmApp = await instantiateApp(resolved.wasm, bridge);
|
|
120
|
-
if (cancelled)
|
|
121
|
-
return;
|
|
122
130
|
setStatusAndNotify("running");
|
|
123
131
|
queueMicrotask(() => {
|
|
124
132
|
if (cancelled)
|
|
125
133
|
return;
|
|
126
|
-
void
|
|
134
|
+
void runOnce(surface).catch((runError) => {
|
|
127
135
|
if (!cancelled) {
|
|
128
136
|
setError(runError);
|
|
129
137
|
}
|
|
@@ -139,6 +147,7 @@ export function TuiPreview(props) {
|
|
|
139
147
|
setup();
|
|
140
148
|
return () => {
|
|
141
149
|
cancelled = true;
|
|
150
|
+
activeBridge = null;
|
|
142
151
|
disposeRenderSurface?.();
|
|
143
152
|
disposeRenderSurface = null;
|
|
144
153
|
};
|
|
@@ -166,7 +175,13 @@ export function TuiPreview(props) {
|
|
|
166
175
|
borderRadius: 6,
|
|
167
176
|
overflow: "hidden",
|
|
168
177
|
...props.style,
|
|
169
|
-
}, children: [_jsx("div", { ref: containerRef, style: {
|
|
178
|
+
}, children: [_jsx("div", { ref: containerRef, style: {
|
|
179
|
+
display: status === "error" ? "none" : "flex",
|
|
180
|
+
justifyContent: "center",
|
|
181
|
+
alignItems: "center",
|
|
182
|
+
width: "100%",
|
|
183
|
+
height: "100%",
|
|
184
|
+
} }), status === "loading" && _jsx("div", { style: overlayStyle, children: "Loading\u2026" }), status === "error" && (_jsxs("div", { style: { ...overlayStyle, color: "#f7768e" }, children: ["Error: ", errorMsg] }))] }));
|
|
170
185
|
}
|
|
171
186
|
const overlayStyle = {
|
|
172
187
|
padding: "1rem",
|
|
Binary file
|
package/dist/core/libghostty.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const EXPECTED_CELL_SIZE = 16;
|
|
2
2
|
const EXPECTED_TERMINAL_CONFIG_SIZE = 80;
|
|
3
|
-
const DEFAULT_WASM_URL = "
|
|
3
|
+
const DEFAULT_WASM_URL = new URL("./ghostty-vt.wasm", import.meta.url).href;
|
|
4
4
|
const FLAG_BOLD = 1 << 0;
|
|
5
5
|
const FLAG_ITALIC = 1 << 1;
|
|
6
6
|
const FLAG_UNDERLINE = 1 << 2;
|
package/dist/core/wasi.d.ts
CHANGED
|
@@ -4,16 +4,20 @@
|
|
|
4
4
|
* Implements a minimal WASI preview1 surface sufficient for interactive TUI apps:
|
|
5
5
|
* - fd_write (stdout/stderr → terminal)
|
|
6
6
|
* - fd_read (stdin ← keyboard input queue)
|
|
7
|
-
* - poll_oneoff (
|
|
7
|
+
* - poll_oneoff (suspending stdin wait via JSPI, needed by crossterm/ratatui)
|
|
8
8
|
* - proc_exit
|
|
9
9
|
* - environ_get / environ_sizes_get
|
|
10
10
|
* - args_get / args_sizes_get
|
|
11
11
|
*/
|
|
12
12
|
import type { WasiOptions } from "../types.js";
|
|
13
|
+
/** Feature-detect JSPI (WebAssembly.Suspending / WebAssembly.promising). */
|
|
14
|
+
export declare const hasJSPI: boolean;
|
|
13
15
|
export declare class WasiBridge {
|
|
14
16
|
private opts;
|
|
15
17
|
private inputQueue;
|
|
16
18
|
private memory;
|
|
19
|
+
/** Resolvers waiting for input to arrive (used by suspending poll_oneoff). */
|
|
20
|
+
private inputWaiters;
|
|
17
21
|
constructor(opts: WasiOptions);
|
|
18
22
|
/** Push keyboard data from the terminal into the app's stdin */
|
|
19
23
|
pushInput(data: string | Uint8Array): void;
|
|
@@ -21,6 +25,8 @@ export declare class WasiBridge {
|
|
|
21
25
|
attachMemory(memory: WebAssembly.Memory): void;
|
|
22
26
|
private view;
|
|
23
27
|
private u8;
|
|
28
|
+
/** Wait until the input queue has data, or until the given timeout (ms). */
|
|
29
|
+
private waitForInput;
|
|
24
30
|
get imports(): WebAssembly.ModuleImports;
|
|
25
31
|
private envEntries;
|
|
26
32
|
}
|
package/dist/core/wasi.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Implements a minimal WASI preview1 surface sufficient for interactive TUI apps:
|
|
5
5
|
* - fd_write (stdout/stderr → terminal)
|
|
6
6
|
* - fd_read (stdin ← keyboard input queue)
|
|
7
|
-
* - poll_oneoff (
|
|
7
|
+
* - poll_oneoff (suspending stdin wait via JSPI, needed by crossterm/ratatui)
|
|
8
8
|
* - proc_exit
|
|
9
9
|
* - environ_get / environ_sizes_get
|
|
10
10
|
* - args_get / args_sizes_get
|
|
@@ -12,14 +12,20 @@
|
|
|
12
12
|
const WASI_ESUCCESS = 0;
|
|
13
13
|
const WASI_EAGAIN = 6;
|
|
14
14
|
const WASI_BADF = 8;
|
|
15
|
+
const WASI_EVENTTYPE_CLOCK = 0;
|
|
15
16
|
const WASI_EVENTTYPE_FD_READ = 1;
|
|
16
17
|
const STDIN_FD = 0;
|
|
17
18
|
const STDOUT_FD = 1;
|
|
18
19
|
const STDERR_FD = 2;
|
|
20
|
+
/** Feature-detect JSPI (WebAssembly.Suspending / WebAssembly.promising). */
|
|
21
|
+
export const hasJSPI = typeof WebAssembly.Suspending === "function" &&
|
|
22
|
+
typeof WebAssembly.promising === "function";
|
|
19
23
|
export class WasiBridge {
|
|
20
24
|
opts;
|
|
21
25
|
inputQueue = [];
|
|
22
26
|
memory;
|
|
27
|
+
/** Resolvers waiting for input to arrive (used by suspending poll_oneoff). */
|
|
28
|
+
inputWaiters = [];
|
|
23
29
|
constructor(opts) {
|
|
24
30
|
this.opts = opts;
|
|
25
31
|
}
|
|
@@ -27,6 +33,10 @@ export class WasiBridge {
|
|
|
27
33
|
pushInput(data) {
|
|
28
34
|
const chunk = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
29
35
|
this.inputQueue.push(chunk);
|
|
36
|
+
// Wake any suspended poll_oneoff calls.
|
|
37
|
+
for (const resolve of this.inputWaiters.splice(0)) {
|
|
38
|
+
resolve();
|
|
39
|
+
}
|
|
30
40
|
}
|
|
31
41
|
/** Attach the WASM instance's memory after instantiation */
|
|
32
42
|
attachMemory(memory) {
|
|
@@ -38,6 +48,24 @@ export class WasiBridge {
|
|
|
38
48
|
u8() {
|
|
39
49
|
return new Uint8Array(this.memory.buffer);
|
|
40
50
|
}
|
|
51
|
+
/** Wait until the input queue has data, or until the given timeout (ms). */
|
|
52
|
+
waitForInput(timeoutMs) {
|
|
53
|
+
if (this.inputQueue.length > 0)
|
|
54
|
+
return Promise.resolve();
|
|
55
|
+
return new Promise((resolve) => {
|
|
56
|
+
const waiter = () => {
|
|
57
|
+
clearTimeout(timer);
|
|
58
|
+
resolve();
|
|
59
|
+
};
|
|
60
|
+
const timer = setTimeout(() => {
|
|
61
|
+
const idx = this.inputWaiters.indexOf(waiter);
|
|
62
|
+
if (idx >= 0)
|
|
63
|
+
this.inputWaiters.splice(idx, 1);
|
|
64
|
+
resolve();
|
|
65
|
+
}, timeoutMs);
|
|
66
|
+
this.inputWaiters.push(waiter);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
41
69
|
// ── WASI imports object ────────────────────────────────────────────────
|
|
42
70
|
get imports() {
|
|
43
71
|
return {
|
|
@@ -138,23 +166,82 @@ export class WasiBridge {
|
|
|
138
166
|
view.setUint32(nreadPtr, nread, true);
|
|
139
167
|
return WASI_ESUCCESS;
|
|
140
168
|
},
|
|
141
|
-
poll_oneoff
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
169
|
+
// poll_oneoff — the critical syscall for event-loop TUI apps.
|
|
170
|
+
//
|
|
171
|
+
// With JSPI: wrapped as a WebAssembly.Suspending import so the WASM
|
|
172
|
+
// instance suspends while we await input, yielding to the browser
|
|
173
|
+
// event loop. This is what allows keyboard events to arrive.
|
|
174
|
+
//
|
|
175
|
+
// Without JSPI: synchronous non-blocking check (legacy/replay mode).
|
|
176
|
+
poll_oneoff: hasJSPI
|
|
177
|
+
? async (inPtr, outPtr, nsubscriptions, neventsPtr) => {
|
|
178
|
+
const view = this.view();
|
|
179
|
+
// Check if any FD_READ subscription can be satisfied immediately.
|
|
180
|
+
let hasFdRead = false;
|
|
181
|
+
let clockTimeoutNs = -1n;
|
|
182
|
+
for (let i = 0; i < nsubscriptions; i++) {
|
|
183
|
+
const subPtr = inPtr + i * 48;
|
|
184
|
+
const type = view.getUint8(subPtr + 8);
|
|
185
|
+
if (type === WASI_EVENTTYPE_FD_READ) {
|
|
186
|
+
hasFdRead = true;
|
|
187
|
+
}
|
|
188
|
+
if (type === WASI_EVENTTYPE_CLOCK) {
|
|
189
|
+
const timeout = view.getBigUint64(subPtr + 24, true);
|
|
190
|
+
if (clockTimeoutNs < 0n || timeout < clockTimeoutNs) {
|
|
191
|
+
clockTimeoutNs = timeout;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// If there's an FD_READ subscription and no data yet, suspend.
|
|
196
|
+
if (hasFdRead && this.inputQueue.length === 0) {
|
|
197
|
+
const timeoutMs = clockTimeoutNs >= 0n
|
|
198
|
+
? Number(clockTimeoutNs / 1000000n)
|
|
199
|
+
: 60_000;
|
|
200
|
+
await this.waitForInput(Math.max(1, Math.min(timeoutMs, 60_000)));
|
|
201
|
+
}
|
|
202
|
+
// Now fill out the events.
|
|
203
|
+
let nevents = 0;
|
|
204
|
+
for (let i = 0; i < nsubscriptions; i++) {
|
|
205
|
+
const subPtr = inPtr + i * 48;
|
|
206
|
+
const type = view.getUint8(subPtr + 8);
|
|
207
|
+
const userdata = view.getBigUint64(subPtr, true);
|
|
208
|
+
if (type === WASI_EVENTTYPE_FD_READ && this.inputQueue.length > 0) {
|
|
209
|
+
const evPtr = outPtr + nevents * 32;
|
|
210
|
+
view.setBigUint64(evPtr, userdata, true);
|
|
211
|
+
view.setUint16(evPtr + 8, 0, true); // error = 0
|
|
212
|
+
view.setUint8(evPtr + 10, type);
|
|
213
|
+
nevents++;
|
|
214
|
+
}
|
|
215
|
+
else if (type === WASI_EVENTTYPE_CLOCK) {
|
|
216
|
+
// Clock subscriptions always fire (we already waited).
|
|
217
|
+
const evPtr = outPtr + nevents * 32;
|
|
218
|
+
view.setBigUint64(evPtr, userdata, true);
|
|
219
|
+
view.setUint16(evPtr + 8, 0, true);
|
|
220
|
+
view.setUint8(evPtr + 10, type);
|
|
221
|
+
nevents++;
|
|
222
|
+
}
|
|
153
223
|
}
|
|
224
|
+
view.setUint32(neventsPtr, nevents, true);
|
|
225
|
+
return WASI_ESUCCESS;
|
|
154
226
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
227
|
+
: (inPtr, outPtr, nsubscriptions, neventsPtr) => {
|
|
228
|
+
// Fallback: synchronous non-blocking check (no JSPI).
|
|
229
|
+
const view = this.view();
|
|
230
|
+
let nevents = 0;
|
|
231
|
+
for (let i = 0; i < nsubscriptions; i++) {
|
|
232
|
+
const subPtr = inPtr + i * 48;
|
|
233
|
+
const type = view.getUint8(subPtr + 8);
|
|
234
|
+
if (type === WASI_EVENTTYPE_FD_READ && this.inputQueue.length > 0) {
|
|
235
|
+
const evPtr = outPtr + nevents * 32;
|
|
236
|
+
view.setBigUint64(evPtr, view.getBigUint64(subPtr, true), true);
|
|
237
|
+
view.setUint16(evPtr + 8, 0, true);
|
|
238
|
+
view.setUint8(evPtr + 10, type);
|
|
239
|
+
nevents++;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
view.setUint32(neventsPtr, nevents, true);
|
|
243
|
+
return WASI_ESUCCESS;
|
|
244
|
+
},
|
|
158
245
|
proc_exit: (code) => {
|
|
159
246
|
this.opts.onExit(code);
|
|
160
247
|
throw new WasiExitError(code);
|
|
@@ -216,18 +303,27 @@ export async function instantiateApp(source, bridge) {
|
|
|
216
303
|
module = await WebAssembly.compile(bytes);
|
|
217
304
|
moduleCache.set(key, module);
|
|
218
305
|
}
|
|
306
|
+
const wasiImports = bridge.imports;
|
|
307
|
+
if (hasJSPI) {
|
|
308
|
+
wasiImports.poll_oneoff = new WebAssembly.Suspending(wasiImports.poll_oneoff);
|
|
309
|
+
}
|
|
219
310
|
const importObject = {
|
|
220
|
-
wasi_snapshot_preview1:
|
|
311
|
+
wasi_snapshot_preview1: wasiImports,
|
|
221
312
|
};
|
|
222
313
|
const instance = await WebAssembly.instantiate(module, importObject);
|
|
223
314
|
bridge.attachMemory(instance.exports.memory);
|
|
224
|
-
const
|
|
225
|
-
if (!
|
|
315
|
+
const rawStart = instance.exports._start;
|
|
316
|
+
if (!rawStart)
|
|
226
317
|
throw new Error("WASM module has no _start export");
|
|
318
|
+
// With JSPI, wrap _start so it returns a Promise that resolves when the
|
|
319
|
+
// WASM app finishes (or suspends on poll_oneoff and resumes later).
|
|
320
|
+
const _start = hasJSPI
|
|
321
|
+
? WebAssembly.promising(rawStart)
|
|
322
|
+
: rawStart;
|
|
227
323
|
return {
|
|
228
324
|
run: async () => {
|
|
229
325
|
try {
|
|
230
|
-
_start();
|
|
326
|
+
await _start();
|
|
231
327
|
}
|
|
232
328
|
catch (e) {
|
|
233
329
|
if (!(e instanceof WasiExitError))
|
package/dist/index.js
CHANGED
|
@@ -1,749 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
var F = (e, t, n) => ot(e, typeof t != "symbol" ? t + "" : t, n);
|
|
4
|
-
import { jsxs as O, jsx as H } from "react/jsx-runtime";
|
|
5
|
-
import { useMemo as at, useRef as P, useState as $, useEffect as N } from "react";
|
|
6
|
-
const S = 16, T = 80, tt = "/ghostty-vt.wasm", ct = 1, lt = 2, ht = 4, W = 8, K = 16, ut = 32, R = 64, Y = 128, ft = [
|
|
7
|
-
"black",
|
|
8
|
-
"red",
|
|
9
|
-
"green",
|
|
10
|
-
"yellow",
|
|
11
|
-
"blue",
|
|
12
|
-
"magenta",
|
|
13
|
-
"cyan",
|
|
14
|
-
"white",
|
|
15
|
-
"brightBlack",
|
|
16
|
-
"brightRed",
|
|
17
|
-
"brightGreen",
|
|
18
|
-
"brightYellow",
|
|
19
|
-
"brightBlue",
|
|
20
|
-
"brightMagenta",
|
|
21
|
-
"brightCyan",
|
|
22
|
-
"brightWhite"
|
|
23
|
-
], wt = {
|
|
24
|
-
background: "#1a1b26",
|
|
25
|
-
foreground: "#a9b1d6",
|
|
26
|
-
cursor: "#c0caf5",
|
|
27
|
-
selectionBackground: "#33467c",
|
|
28
|
-
selectionForeground: "#c0caf5",
|
|
29
|
-
black: "#15161e",
|
|
30
|
-
red: "#f7768e",
|
|
31
|
-
green: "#9ece6a",
|
|
32
|
-
yellow: "#e0af68",
|
|
33
|
-
blue: "#7aa2f7",
|
|
34
|
-
magenta: "#bb9af7",
|
|
35
|
-
cyan: "#7dcfff",
|
|
36
|
-
white: "#a9b1d6",
|
|
37
|
-
brightBlack: "#414868",
|
|
38
|
-
brightRed: "#f7768e",
|
|
39
|
-
brightGreen: "#9ece6a",
|
|
40
|
-
brightYellow: "#e0af68",
|
|
41
|
-
brightBlue: "#7aa2f7",
|
|
42
|
-
brightMagenta: "#bb9af7",
|
|
43
|
-
brightCyan: "#7dcfff",
|
|
44
|
-
brightWhite: "#c0caf5"
|
|
45
|
-
};
|
|
46
|
-
class mt {
|
|
47
|
-
constructor(t, n) {
|
|
48
|
-
this.wasm = t, this.abi = n;
|
|
49
|
-
}
|
|
50
|
-
createTerminal(t, n, r) {
|
|
51
|
-
const o = this.wasm.ghostty_wasm_alloc_u8_array(this.abi.terminalConfigSize);
|
|
52
|
-
if (!o)
|
|
53
|
-
throw new Error("Failed to allocate terminal config.");
|
|
54
|
-
try {
|
|
55
|
-
const i = new DataView(this.wasm.memory.buffer);
|
|
56
|
-
let s = o;
|
|
57
|
-
i.setUint32(s, 1e4, !0), s += 4, i.setUint32(s, z(r.foreground), !0), s += 4, i.setUint32(s, z(r.background), !0), s += 4, i.setUint32(s, z(r.cursor), !0), s += 4;
|
|
58
|
-
for (const c of ft)
|
|
59
|
-
i.setUint32(s, z(r[c]), !0), s += 4;
|
|
60
|
-
const a = this.wasm.ghostty_terminal_new_with_config(t, n, o);
|
|
61
|
-
if (!a)
|
|
62
|
-
throw new Error("Failed to create libghostty terminal.");
|
|
63
|
-
return new dt(this.wasm, a, t, n, this.abi.cellSize);
|
|
64
|
-
} finally {
|
|
65
|
-
this.wasm.ghostty_wasm_free_u8_array(o, this.abi.terminalConfigSize);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
class dt {
|
|
70
|
-
constructor(t, n, r, o, i) {
|
|
71
|
-
F(this, "viewportPtr", 0);
|
|
72
|
-
F(this, "viewportLen", 0);
|
|
73
|
-
this.wasm = t, this.handle = n, this.cols = r, this.rows = o, this.cellSize = i;
|
|
74
|
-
}
|
|
75
|
-
write(t) {
|
|
76
|
-
const n = typeof t == "string" ? new TextEncoder().encode(t) : t;
|
|
77
|
-
if (n.length === 0) return;
|
|
78
|
-
const r = this.wasm.ghostty_wasm_alloc_u8_array(n.length);
|
|
79
|
-
if (!r)
|
|
80
|
-
throw new Error("Failed to allocate libghostty write buffer.");
|
|
81
|
-
try {
|
|
82
|
-
new Uint8Array(this.wasm.memory.buffer).set(n, r), this.wasm.ghostty_terminal_write(this.handle, r, n.length);
|
|
83
|
-
} finally {
|
|
84
|
-
this.wasm.ghostty_wasm_free_u8_array(r, n.length);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
resize(t, n) {
|
|
88
|
-
t === this.cols && n === this.rows || (this.cols = t, this.rows = n, this.wasm.ghostty_terminal_resize(this.handle, t, n), this.releaseViewport());
|
|
89
|
-
}
|
|
90
|
-
hasResponse() {
|
|
91
|
-
return this.wasm.ghostty_terminal_has_response(this.handle);
|
|
92
|
-
}
|
|
93
|
-
isDirty() {
|
|
94
|
-
return this.wasm.ghostty_render_state_update(this.handle) !== 0;
|
|
95
|
-
}
|
|
96
|
-
readResponse(t = 4096) {
|
|
97
|
-
const n = this.wasm.ghostty_wasm_alloc_u8_array(t);
|
|
98
|
-
if (!n)
|
|
99
|
-
throw new Error("Failed to allocate libghostty response buffer.");
|
|
100
|
-
try {
|
|
101
|
-
const r = this.wasm.ghostty_terminal_read_response(this.handle, n, t);
|
|
102
|
-
if (r <= 0) return null;
|
|
103
|
-
const o = new Uint8Array(this.wasm.memory.buffer, n, r);
|
|
104
|
-
return new TextDecoder().decode(o);
|
|
105
|
-
} finally {
|
|
106
|
-
this.wasm.ghostty_wasm_free_u8_array(n, t);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
getViewportData() {
|
|
110
|
-
const t = this.wasm.ghostty_render_state_get_cols(this.handle), n = this.wasm.ghostty_render_state_get_rows(this.handle);
|
|
111
|
-
this.cols = t, this.rows = n;
|
|
112
|
-
const r = Math.max(1, t * n * this.cellSize);
|
|
113
|
-
if ((r > this.viewportLen || this.viewportPtr === 0) && (this.releaseViewport(), this.viewportPtr = this.wasm.ghostty_wasm_alloc_u8_array(r), this.viewportLen = r, !this.viewportPtr))
|
|
114
|
-
throw new Error("Failed to allocate libghostty viewport buffer.");
|
|
115
|
-
const o = this.wasm.ghostty_render_state_get_viewport(this.handle, this.viewportPtr, this.viewportLen), i = new Uint8Array(o);
|
|
116
|
-
if (o > 0) {
|
|
117
|
-
const s = new Uint8Array(this.wasm.memory.buffer, this.viewportPtr, o);
|
|
118
|
-
i.set(s);
|
|
119
|
-
}
|
|
120
|
-
return this.wasm.ghostty_render_state_mark_clean(this.handle), {
|
|
121
|
-
cols: t,
|
|
122
|
-
rows: n,
|
|
123
|
-
buffer: i
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
dispose() {
|
|
127
|
-
this.releaseViewport(), this.wasm.ghostty_terminal_free(this.handle);
|
|
128
|
-
}
|
|
129
|
-
releaseViewport() {
|
|
130
|
-
this.viewportPtr !== 0 && (this.wasm.ghostty_wasm_free_u8_array(this.viewportPtr, this.viewportLen), this.viewportPtr = 0, this.viewportLen = 0);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
class gt {
|
|
134
|
-
constructor(t, n, r, o, i, s) {
|
|
135
|
-
F(this, "ctx");
|
|
136
|
-
F(this, "dpr");
|
|
137
|
-
F(this, "metrics");
|
|
138
|
-
this.canvas = t, this.cols = n, this.rows = r, this.fontSize = o, this.fontFamily = i, this.theme = s;
|
|
139
|
-
const a = t.getContext("2d");
|
|
140
|
-
if (!a)
|
|
141
|
-
throw new Error("Failed to create 2D canvas context.");
|
|
142
|
-
this.ctx = a, this.dpr = window.devicePixelRatio || 1, this.metrics = this.measureFont(), this.resizeCanvas(n, r);
|
|
143
|
-
}
|
|
144
|
-
get cellSize() {
|
|
145
|
-
return { w: this.metrics.width, h: this.metrics.height };
|
|
146
|
-
}
|
|
147
|
-
render(t) {
|
|
148
|
-
(t.cols !== this.cols || t.rows !== this.rows) && (this.cols = t.cols, this.rows = t.rows, this.resizeCanvas(t.cols, t.rows));
|
|
149
|
-
const { cols: n, rows: r, buffer: o } = t, i = new DataView(o.buffer, o.byteOffset, o.byteLength), s = this.ctx, a = this.metrics.width, c = this.metrics.height;
|
|
150
|
-
s.fillStyle = this.theme.background, s.fillRect(0, 0, n * a, r * c);
|
|
151
|
-
for (let l = 0; l < r; l++)
|
|
152
|
-
for (let u = 0; u < n; u++) {
|
|
153
|
-
const h = (l * n + u) * S, f = i.getUint8(h + 11);
|
|
154
|
-
if (f === 0) continue;
|
|
155
|
-
const w = i.getUint8(h + 10), b = L(i, h + 4), y = L(i, h + 7), _ = x(w, K), g = x(w, W) ? D(b) : this.theme.foreground, d = x(w, R) ? D(y) : this.theme.background;
|
|
156
|
-
(_ || x(w, R)) && (s.fillStyle = _ ? g : d, s.fillRect(u * a, l * c, f * a, c));
|
|
157
|
-
}
|
|
158
|
-
for (let l = 0; l < r; l++)
|
|
159
|
-
for (let u = 0; u < n; u++) {
|
|
160
|
-
const h = (l * n + u) * S, f = i.getUint8(h + 11);
|
|
161
|
-
if (f === 0) continue;
|
|
162
|
-
const w = i.getUint8(h + 10);
|
|
163
|
-
if (x(w, ut)) continue;
|
|
164
|
-
const b = i.getUint32(h, !0);
|
|
165
|
-
if (b === 0) continue;
|
|
166
|
-
const y = x(w, K), _ = L(i, h + 4), g = L(i, h + 7), d = x(w, W) ? D(_) : this.theme.foreground, m = x(w, R) ? D(g) : this.theme.background;
|
|
167
|
-
s.fillStyle = y ? m : d;
|
|
168
|
-
let p = "";
|
|
169
|
-
x(w, lt) && (p += "italic "), x(w, ct) && (p += "bold "), s.font = `${p}${this.fontSize}px ${this.fontFamily}`, x(w, Y) && (s.globalAlpha = 0.5);
|
|
170
|
-
const E = Et(b);
|
|
171
|
-
if (s.fillText(E, u * a, l * c + this.metrics.baseline), x(w, Y) && (s.globalAlpha = 1), x(w, ht)) {
|
|
172
|
-
const v = l * c + this.metrics.baseline + 2;
|
|
173
|
-
s.strokeStyle = s.fillStyle, s.lineWidth = 1, s.beginPath(), s.moveTo(u * a, v), s.lineTo(u * a + f * a, v), s.stroke();
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
dispose() {
|
|
178
|
-
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
179
|
-
}
|
|
180
|
-
measureFont() {
|
|
181
|
-
this.ctx.font = `${this.fontSize}px ${this.fontFamily}`;
|
|
182
|
-
const t = this.ctx.measureText("M"), n = Math.ceil(t.width), r = t.actualBoundingBoxAscent || this.fontSize * 0.8, o = t.actualBoundingBoxDescent || this.fontSize * 0.2;
|
|
183
|
-
return {
|
|
184
|
-
width: n,
|
|
185
|
-
height: Math.ceil(r + o) + 2,
|
|
186
|
-
baseline: Math.ceil(r) + 1
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
resizeCanvas(t, n) {
|
|
190
|
-
const r = t * this.metrics.width, o = n * this.metrics.height;
|
|
191
|
-
this.canvas.width = Math.max(1, Math.floor(r * this.dpr)), this.canvas.height = Math.max(1, Math.floor(o * this.dpr)), this.canvas.style.width = `${r}px`, this.canvas.style.height = `${o}px`, this.ctx.setTransform(this.dpr, 0, 0, this.dpr, 0, 0), this.ctx.textBaseline = "alphabetic", this.ctx.textAlign = "left";
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
function et(e, t) {
|
|
195
|
-
const r = document.createElement("canvas").getContext("2d");
|
|
196
|
-
if (!r)
|
|
197
|
-
return {
|
|
198
|
-
w: Math.ceil(e * 0.6),
|
|
199
|
-
h: Math.ceil(e * 1.2)
|
|
200
|
-
};
|
|
201
|
-
r.font = `${e}px ${t}`;
|
|
202
|
-
const o = r.measureText("M"), i = Math.ceil(o.width), s = o.actualBoundingBoxAscent || e * 0.8, a = o.actualBoundingBoxDescent || e * 0.2;
|
|
203
|
-
return { w: i, h: Math.ceil(s + a) + 2 };
|
|
204
|
-
}
|
|
205
|
-
const Q = /* @__PURE__ */ new Map();
|
|
206
|
-
function yt(e = tt) {
|
|
207
|
-
const t = e.toString(), n = Q.get(t);
|
|
208
|
-
if (n)
|
|
209
|
-
return n;
|
|
210
|
-
const r = (async () => {
|
|
211
|
-
const o = await fetch(e);
|
|
212
|
-
if (!o.ok)
|
|
213
|
-
throw new Error(`Failed to load libghostty wasm: ${o.status} ${o.statusText}`);
|
|
214
|
-
const i = await o.arrayBuffer();
|
|
215
|
-
if (i.byteLength === 0)
|
|
216
|
-
throw new Error("libghostty wasm is empty.");
|
|
217
|
-
const s = await WebAssembly.compile(i), c = (await WebAssembly.instantiate(s, {
|
|
218
|
-
env: {
|
|
219
|
-
log: () => {
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
})).exports;
|
|
223
|
-
if (!c.memory || !c.ghostty_terminal_new || !c.ghostty_render_state_get_viewport)
|
|
224
|
-
throw new Error("Invalid libghostty wasm exports.");
|
|
225
|
-
return bt(c), new mt(c, {
|
|
226
|
-
cellSize: S,
|
|
227
|
-
terminalConfigSize: T
|
|
228
|
-
});
|
|
229
|
-
})();
|
|
230
|
-
return Q.set(t, r), r;
|
|
231
|
-
}
|
|
232
|
-
async function _t(e) {
|
|
233
|
-
var _, g;
|
|
234
|
-
const t = await yt(e.wasmUrl ?? tt), n = { ...wt, ...e.theme };
|
|
235
|
-
let r, o;
|
|
236
|
-
const i = et(e.fontSize, e.fontFamily);
|
|
237
|
-
e.widthPx != null && e.heightPx != null ? (r = Math.max(1, Math.floor(e.widthPx / i.w)), o = Math.max(1, Math.floor(e.heightPx / i.h))) : (r = e.cols ?? 80, o = e.rows ?? 24), e.container.innerHTML = "";
|
|
238
|
-
const s = document.createElement("canvas");
|
|
239
|
-
s.style.display = "block", s.style.outline = "none", s.tabIndex = e.interactive ? 0 : -1, e.container.appendChild(s);
|
|
240
|
-
const a = t.createTerminal(r, o, n), c = new gt(
|
|
241
|
-
s,
|
|
242
|
-
r,
|
|
243
|
-
o,
|
|
244
|
-
e.fontSize,
|
|
245
|
-
e.fontFamily,
|
|
246
|
-
n
|
|
247
|
-
);
|
|
248
|
-
e.showCursor || a.write("\x1B[?25l"), c.render(a.getViewportData());
|
|
249
|
-
const l = ((_ = window.requestAnimationFrame) == null ? void 0 : _.bind(window)) ?? ((d) => window.setTimeout(d, 16)), u = ((g = window.cancelAnimationFrame) == null ? void 0 : g.bind(window)) ?? window.clearTimeout.bind(window);
|
|
250
|
-
let h = null, f = !1;
|
|
251
|
-
const w = () => {
|
|
252
|
-
h = null, !(f || !a.isDirty()) && c.render(a.getViewportData());
|
|
253
|
-
}, b = () => {
|
|
254
|
-
h !== null || f || (h = l(w));
|
|
255
|
-
}, y = e.interactive ? vt(s, (d) => {
|
|
256
|
-
var m;
|
|
257
|
-
return (m = e.onInput) == null ? void 0 : m.call(e, d);
|
|
258
|
-
}) : () => {
|
|
259
|
-
};
|
|
260
|
-
return {
|
|
261
|
-
cols: r,
|
|
262
|
-
rows: o,
|
|
263
|
-
cellSize: c.cellSize,
|
|
264
|
-
write(d) {
|
|
265
|
-
if (f) return;
|
|
266
|
-
const m = e.convertEol ? pt(d) : d;
|
|
267
|
-
a.write(m), b();
|
|
268
|
-
},
|
|
269
|
-
drainResponses() {
|
|
270
|
-
if (f) return [];
|
|
271
|
-
const d = [];
|
|
272
|
-
for (; a.hasResponse(); ) {
|
|
273
|
-
const m = a.readResponse();
|
|
274
|
-
if (!m) break;
|
|
275
|
-
d.push(m);
|
|
276
|
-
}
|
|
277
|
-
return d;
|
|
278
|
-
},
|
|
279
|
-
dispose() {
|
|
280
|
-
f = !0, h !== null && (u(h), h = null), y(), c.dispose(), a.dispose(), s.parentElement === e.container && e.container.removeChild(s);
|
|
281
|
-
}
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
function bt(e) {
|
|
285
|
-
const t = e.ghostty_wasm_alloc_u8_array(T);
|
|
286
|
-
if (!t)
|
|
287
|
-
throw new Error("Failed to allocate ABI probe config buffer.");
|
|
288
|
-
let n = 0, r = 0, o = 0, i = 0;
|
|
289
|
-
const s = 1122867, a = 4478310, [c, l, u] = X(s), [h, f, w] = X(a);
|
|
290
|
-
try {
|
|
291
|
-
new Uint8Array(e.memory.buffer, t, T).fill(0);
|
|
292
|
-
const b = new DataView(e.memory.buffer, t, T);
|
|
293
|
-
if (b.setUint32(0, 16, !0), b.setUint32(4, s, !0), b.setUint32(8, a, !0), b.setUint32(12, 7833753, !0), n = e.ghostty_terminal_new_with_config(2, 1, t), !n)
|
|
294
|
-
throw new Error("Failed to create ABI probe terminal.");
|
|
295
|
-
const y = new TextEncoder().encode("\x1B[7mX");
|
|
296
|
-
if (o = y.length, r = e.ghostty_wasm_alloc_u8_array(o), !r)
|
|
297
|
-
throw new Error("Failed to allocate ABI probe write buffer.");
|
|
298
|
-
if (new Uint8Array(e.memory.buffer, r, o).set(y), e.ghostty_terminal_write(n, r, o), i = e.ghostty_wasm_alloc_u8_array(S), !i)
|
|
299
|
-
throw new Error("Failed to allocate ABI probe viewport buffer.");
|
|
300
|
-
const _ = e.ghostty_render_state_get_viewport(n, i, S);
|
|
301
|
-
if (_ !== S)
|
|
302
|
-
throw new Error(
|
|
303
|
-
`Incompatible libghostty ABI: expected cell size ${S}, got ${_}.`
|
|
304
|
-
);
|
|
305
|
-
const g = new DataView(e.memory.buffer, i, S), d = g.getUint8(10), m = x(d, W), p = x(d, R), E = g.getUint8(4) === c && g.getUint8(5) === l && g.getUint8(6) === u, v = g.getUint8(7) === h && g.getUint8(8) === f && g.getUint8(9) === w;
|
|
306
|
-
if (!m || !p || !E || !v)
|
|
307
|
-
throw new Error("Incompatible libghostty ABI: terminal config layout mismatch.");
|
|
308
|
-
} finally {
|
|
309
|
-
i && e.ghostty_wasm_free_u8_array(i, S), r && o > 0 && e.ghostty_wasm_free_u8_array(r, o), n && e.ghostty_terminal_free(n), e.ghostty_wasm_free_u8_array(t, T);
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
function pt(e) {
|
|
313
|
-
return e.replace(/\r?\n/g, `\r
|
|
314
|
-
`);
|
|
315
|
-
}
|
|
316
|
-
function z(e) {
|
|
317
|
-
if (e.startsWith("#")) {
|
|
318
|
-
let i = e.slice(1);
|
|
319
|
-
i.length === 3 && (i = `${i[0]}${i[0]}${i[1]}${i[1]}${i[2]}${i[2]}`);
|
|
320
|
-
const s = Number.parseInt(i, 16);
|
|
321
|
-
return Number.isNaN(s) ? 0 : s;
|
|
322
|
-
}
|
|
323
|
-
const t = e.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
|
|
324
|
-
if (!t) return 0;
|
|
325
|
-
const n = Number.parseInt(t[1], 10), r = Number.parseInt(t[2], 10), o = Number.parseInt(t[3], 10);
|
|
326
|
-
return n << 16 | r << 8 | o;
|
|
327
|
-
}
|
|
328
|
-
function X(e) {
|
|
329
|
-
return [e >> 16 & 255, e >> 8 & 255, e & 255];
|
|
330
|
-
}
|
|
331
|
-
function L(e, t) {
|
|
332
|
-
return {
|
|
333
|
-
r: e.getUint8(t),
|
|
334
|
-
g: e.getUint8(t + 1),
|
|
335
|
-
b: e.getUint8(t + 2)
|
|
336
|
-
};
|
|
337
|
-
}
|
|
338
|
-
function x(e, t) {
|
|
339
|
-
return (e & t) !== 0;
|
|
340
|
-
}
|
|
341
|
-
function D(e) {
|
|
342
|
-
return `rgb(${e.r}, ${e.g}, ${e.b})`;
|
|
343
|
-
}
|
|
344
|
-
function Et(e) {
|
|
345
|
-
try {
|
|
346
|
-
return String.fromCodePoint(e);
|
|
347
|
-
} catch {
|
|
348
|
-
return " ";
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
function vt(e, t) {
|
|
352
|
-
const n = () => e.focus(), r = (i) => {
|
|
353
|
-
var a;
|
|
354
|
-
const s = (a = i.clipboardData) == null ? void 0 : a.getData("text");
|
|
355
|
-
s && (i.preventDefault(), t(s));
|
|
356
|
-
}, o = (i) => {
|
|
357
|
-
const s = xt(i);
|
|
358
|
-
s && (i.preventDefault(), t(s));
|
|
359
|
-
};
|
|
360
|
-
return e.addEventListener("mousedown", n), e.addEventListener("paste", r), e.addEventListener("keydown", o), () => {
|
|
361
|
-
e.removeEventListener("mousedown", n), e.removeEventListener("paste", r), e.removeEventListener("keydown", o);
|
|
362
|
-
};
|
|
363
|
-
}
|
|
364
|
-
function xt(e) {
|
|
365
|
-
if (e.isComposing || e.metaKey) return null;
|
|
366
|
-
let t = null;
|
|
367
|
-
switch (e.key) {
|
|
368
|
-
case "Enter":
|
|
369
|
-
t = "\r";
|
|
370
|
-
break;
|
|
371
|
-
case "Backspace":
|
|
372
|
-
t = "";
|
|
373
|
-
break;
|
|
374
|
-
case "Tab":
|
|
375
|
-
t = e.shiftKey ? "\x1B[Z" : " ";
|
|
376
|
-
break;
|
|
377
|
-
case "Escape":
|
|
378
|
-
t = "\x1B";
|
|
379
|
-
break;
|
|
380
|
-
case "ArrowUp":
|
|
381
|
-
t = "\x1B[A";
|
|
382
|
-
break;
|
|
383
|
-
case "ArrowDown":
|
|
384
|
-
t = "\x1B[B";
|
|
385
|
-
break;
|
|
386
|
-
case "ArrowRight":
|
|
387
|
-
t = "\x1B[C";
|
|
388
|
-
break;
|
|
389
|
-
case "ArrowLeft":
|
|
390
|
-
t = "\x1B[D";
|
|
391
|
-
break;
|
|
392
|
-
case "Home":
|
|
393
|
-
t = "\x1B[H";
|
|
394
|
-
break;
|
|
395
|
-
case "End":
|
|
396
|
-
t = "\x1B[F";
|
|
397
|
-
break;
|
|
398
|
-
case "Delete":
|
|
399
|
-
t = "\x1B[3~";
|
|
400
|
-
break;
|
|
401
|
-
case "PageUp":
|
|
402
|
-
t = "\x1B[5~";
|
|
403
|
-
break;
|
|
404
|
-
case "PageDown":
|
|
405
|
-
t = "\x1B[6~";
|
|
406
|
-
break;
|
|
407
|
-
}
|
|
408
|
-
return !t && e.ctrlKey && (t = At(e.key)), !t && e.key.length === 1 && !e.ctrlKey && (t = e.key), t ? e.altKey && !t.startsWith("\x1B") ? `\x1B${t}` : t : null;
|
|
409
|
-
}
|
|
410
|
-
function At(e) {
|
|
411
|
-
if (e.length !== 1) return null;
|
|
412
|
-
const t = e.toUpperCase();
|
|
413
|
-
if (t >= "A" && t <= "Z")
|
|
414
|
-
return String.fromCharCode(t.charCodeAt(0) - 64);
|
|
415
|
-
switch (e) {
|
|
416
|
-
case "@":
|
|
417
|
-
case " ":
|
|
418
|
-
return "\0";
|
|
419
|
-
case "[":
|
|
420
|
-
return "\x1B";
|
|
421
|
-
case "\\":
|
|
422
|
-
return "";
|
|
423
|
-
case "]":
|
|
424
|
-
return "";
|
|
425
|
-
case "^":
|
|
426
|
-
return "";
|
|
427
|
-
case "_":
|
|
428
|
-
return "";
|
|
429
|
-
default:
|
|
430
|
-
return null;
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
const Z = { cols: 80, rows: 24 }, Ut = {}, St = [];
|
|
434
|
-
function Ft(e) {
|
|
435
|
-
return e === "static" ? "static" : "interactive";
|
|
436
|
-
}
|
|
437
|
-
function Bt(e) {
|
|
438
|
-
const t = e ?? St;
|
|
439
|
-
return typeof t == "function" ? t : () => t;
|
|
440
|
-
}
|
|
441
|
-
function Ct(e) {
|
|
442
|
-
var o, i, s, a, c, l, u;
|
|
443
|
-
const t = e.fit ?? (e.size ? "none" : "container"), n = {
|
|
444
|
-
cols: Math.max(1, ((o = e.size) == null ? void 0 : o.cols) ?? Z.cols),
|
|
445
|
-
rows: Math.max(1, ((i = e.size) == null ? void 0 : i.rows) ?? Z.rows)
|
|
446
|
-
}, r = Ft(e.mode);
|
|
447
|
-
return {
|
|
448
|
-
wasm: e.wasm,
|
|
449
|
-
env: e.env ?? Ut,
|
|
450
|
-
interactive: r === "static" ? !1 : e.interactive ?? !0,
|
|
451
|
-
mode: r,
|
|
452
|
-
fit: t,
|
|
453
|
-
size: n,
|
|
454
|
-
terminal: {
|
|
455
|
-
fontSize: ((s = e.terminal) == null ? void 0 : s.fontSize) ?? 14,
|
|
456
|
-
fontFamily: ((a = e.terminal) == null ? void 0 : a.fontFamily) ?? "monospace",
|
|
457
|
-
wasmUrl: (c = e.terminal) == null ? void 0 : c.wasmUrl,
|
|
458
|
-
convertEol: ((l = e.terminal) == null ? void 0 : l.convertEol) ?? !0,
|
|
459
|
-
theme: (u = e.terminal) == null ? void 0 : u.theme
|
|
460
|
-
},
|
|
461
|
-
resolveArgv: Bt(e.argv),
|
|
462
|
-
onExit: e.onExit,
|
|
463
|
-
onError: e.onError,
|
|
464
|
-
onStatusChange: e.onStatusChange
|
|
465
|
-
};
|
|
466
|
-
}
|
|
467
|
-
const A = 0, Tt = 6, C = 8, Mt = 1, kt = 0, j = 1, It = 2;
|
|
468
|
-
class zt {
|
|
469
|
-
constructor(t) {
|
|
470
|
-
F(this, "inputQueue", []);
|
|
471
|
-
F(this, "memory");
|
|
472
|
-
this.opts = t;
|
|
473
|
-
}
|
|
474
|
-
/** Push keyboard data from the terminal into the app's stdin */
|
|
475
|
-
pushInput(t) {
|
|
476
|
-
const n = typeof t == "string" ? new TextEncoder().encode(t) : t;
|
|
477
|
-
this.inputQueue.push(n);
|
|
478
|
-
}
|
|
479
|
-
/** Attach the WASM instance's memory after instantiation */
|
|
480
|
-
attachMemory(t) {
|
|
481
|
-
this.memory = t;
|
|
482
|
-
}
|
|
483
|
-
view() {
|
|
484
|
-
return new DataView(this.memory.buffer);
|
|
485
|
-
}
|
|
486
|
-
u8() {
|
|
487
|
-
return new Uint8Array(this.memory.buffer);
|
|
488
|
-
}
|
|
489
|
-
// ── WASI imports object ────────────────────────────────────────────────
|
|
490
|
-
get imports() {
|
|
491
|
-
return {
|
|
492
|
-
args_sizes_get: (t, n) => {
|
|
493
|
-
const { args: r } = this.opts, o = new TextEncoder(), i = r.reduce((s, a) => s + o.encode(a).length + 1, 0);
|
|
494
|
-
return this.view().setUint32(t, r.length, !0), this.view().setUint32(n, i, !0), A;
|
|
495
|
-
},
|
|
496
|
-
args_get: (t, n) => {
|
|
497
|
-
const r = new TextEncoder(), o = this.u8(), i = this.view();
|
|
498
|
-
let s = n;
|
|
499
|
-
return this.opts.args.forEach((a, c) => {
|
|
500
|
-
const l = r.encode(a);
|
|
501
|
-
o.set(l, s), o[s + l.length] = 0, i.setUint32(t + c * 4, s, !0), s += l.length + 1;
|
|
502
|
-
}), A;
|
|
503
|
-
},
|
|
504
|
-
environ_sizes_get: (t, n) => {
|
|
505
|
-
const r = this.envEntries(), o = new TextEncoder(), i = r.reduce((s, a) => s + o.encode(a).length + 1, 0);
|
|
506
|
-
return this.view().setUint32(t, r.length, !0), this.view().setUint32(n, i, !0), A;
|
|
507
|
-
},
|
|
508
|
-
environ_get: (t, n) => {
|
|
509
|
-
const r = new TextEncoder(), o = this.u8(), i = this.view();
|
|
510
|
-
let s = n;
|
|
511
|
-
return this.envEntries().forEach((a, c) => {
|
|
512
|
-
const l = r.encode(a);
|
|
513
|
-
o.set(l, s), o[s + l.length] = 0, i.setUint32(t + c * 4, s, !0), s += l.length + 1;
|
|
514
|
-
}), A;
|
|
515
|
-
},
|
|
516
|
-
fd_write: (t, n, r, o) => {
|
|
517
|
-
if (t !== j && t !== It) return C;
|
|
518
|
-
const i = this.view(), s = this.u8();
|
|
519
|
-
let a = 0;
|
|
520
|
-
const c = [];
|
|
521
|
-
for (let h = 0; h < r; h++) {
|
|
522
|
-
const f = i.getUint32(n + h * 8, !0), w = i.getUint32(n + h * 8 + 4, !0);
|
|
523
|
-
c.push(s.slice(f, f + w)), a += w;
|
|
524
|
-
}
|
|
525
|
-
const l = new Uint8Array(a);
|
|
526
|
-
let u = 0;
|
|
527
|
-
for (const h of c)
|
|
528
|
-
l.set(h, u), u += h.length;
|
|
529
|
-
return t === j ? this.opts.stdout(l) : this.opts.stderr(l), i.setUint32(o, a, !0), A;
|
|
530
|
-
},
|
|
531
|
-
fd_read: (t, n, r, o) => {
|
|
532
|
-
if (t !== kt) return C;
|
|
533
|
-
const i = this.inputQueue[0];
|
|
534
|
-
if (!i) return Tt;
|
|
535
|
-
const s = this.view(), a = this.u8();
|
|
536
|
-
let c = 0;
|
|
537
|
-
for (let l = 0; l < r && c < i.length; l++) {
|
|
538
|
-
const u = s.getUint32(n + l * 8, !0), h = s.getUint32(n + l * 8 + 4, !0), f = Math.min(h, i.length - c);
|
|
539
|
-
a.set(i.subarray(c, c + f), u), c += f;
|
|
540
|
-
}
|
|
541
|
-
return c >= i.length ? this.inputQueue.shift() : c > 0 && (this.inputQueue[0] = i.subarray(c)), s.setUint32(o, c, !0), A;
|
|
542
|
-
},
|
|
543
|
-
poll_oneoff: (t, n, r, o) => {
|
|
544
|
-
const i = this.view();
|
|
545
|
-
let s = 0;
|
|
546
|
-
for (let a = 0; a < r; a++) {
|
|
547
|
-
const c = t + a * 48, l = i.getUint8(c + 8);
|
|
548
|
-
if (l === Mt && this.inputQueue.length > 0) {
|
|
549
|
-
const u = n + s * 32;
|
|
550
|
-
i.setBigUint64(u, i.getBigUint64(c, !0), !0), i.setUint16(u + 8, 0, !0), i.setUint8(u + 10, l), s++;
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
return i.setUint32(o, s, !0), A;
|
|
554
|
-
},
|
|
555
|
-
proc_exit: (t) => {
|
|
556
|
-
throw this.opts.onExit(t), new nt(t);
|
|
557
|
-
},
|
|
558
|
-
random_get: (t, n) => (crypto.getRandomValues(new Uint8Array(this.memory.buffer, t, n)), A),
|
|
559
|
-
// Stubs for calls TUI apps may make but we don't need to implement.
|
|
560
|
-
fd_close: () => A,
|
|
561
|
-
fd_seek: () => A,
|
|
562
|
-
fd_fdstat_get: (t, n) => (this.view().setUint8(n, t <= 2 ? 2 : 0), A),
|
|
563
|
-
fd_prestat_get: () => C,
|
|
564
|
-
fd_prestat_dir_name: () => C,
|
|
565
|
-
path_open: () => C,
|
|
566
|
-
sched_yield: () => A,
|
|
567
|
-
clock_time_get: (t, n, r) => {
|
|
568
|
-
const o = BigInt(Date.now()) * 1000000n;
|
|
569
|
-
return this.view().setBigUint64(r, o, !0), A;
|
|
570
|
-
}
|
|
571
|
-
};
|
|
572
|
-
}
|
|
573
|
-
envEntries() {
|
|
574
|
-
const t = {
|
|
575
|
-
TERM: "xterm-256color",
|
|
576
|
-
COLORTERM: "truecolor",
|
|
577
|
-
...this.opts.env
|
|
578
|
-
};
|
|
579
|
-
return Object.entries(t).map(([n, r]) => `${n}=${r}`);
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
class nt extends Error {
|
|
583
|
-
constructor(t) {
|
|
584
|
-
super(`WASI exit: ${t}`), this.code = t;
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
const q = /* @__PURE__ */ new Map();
|
|
588
|
-
async function Lt(e, t) {
|
|
589
|
-
const n = e.toString();
|
|
590
|
-
let r = q.get(n);
|
|
591
|
-
if (!r) {
|
|
592
|
-
const a = await fetch(e);
|
|
593
|
-
if (!a.ok)
|
|
594
|
-
throw new Error(`Failed to load app wasm: ${a.status} ${a.statusText}`);
|
|
595
|
-
const c = await a.arrayBuffer();
|
|
596
|
-
if (c.byteLength === 0)
|
|
597
|
-
throw new Error("App wasm is empty.");
|
|
598
|
-
r = await WebAssembly.compile(c), q.set(n, r);
|
|
599
|
-
}
|
|
600
|
-
const o = {
|
|
601
|
-
wasi_snapshot_preview1: t.imports
|
|
602
|
-
}, i = await WebAssembly.instantiate(r, o);
|
|
603
|
-
t.attachMemory(i.exports.memory);
|
|
604
|
-
const s = i.exports._start;
|
|
605
|
-
if (!s) throw new Error("WASM module has no _start export");
|
|
606
|
-
return {
|
|
607
|
-
run: async () => {
|
|
608
|
-
try {
|
|
609
|
-
s();
|
|
610
|
-
} catch (a) {
|
|
611
|
-
if (!(a instanceof nt)) throw a;
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
};
|
|
615
|
-
}
|
|
616
|
-
function $t(e) {
|
|
617
|
-
var h;
|
|
618
|
-
const t = at(() => Ct(e), [e]), n = P(null), r = P(null), [o, i] = $("loading"), [s, a] = $(""), c = P(null), [l, u] = $(
|
|
619
|
-
t.fit === "container" ? null : t.size
|
|
620
|
-
);
|
|
621
|
-
return N(() => {
|
|
622
|
-
u(t.fit === "container" ? null : t.size);
|
|
623
|
-
}, [t.fit, t.size.cols, t.size.rows]), N(() => {
|
|
624
|
-
if (t.fit !== "container" || !n.current) return;
|
|
625
|
-
const f = n.current, w = et(t.terminal.fontSize, t.terminal.fontFamily), b = (g, d) => {
|
|
626
|
-
var M, k;
|
|
627
|
-
const m = ((M = c.current) == null ? void 0 : M.w) ?? w.w, p = ((k = c.current) == null ? void 0 : k.h) ?? w.h, E = Math.max(1, Math.floor(g / m)), v = Math.max(1, Math.floor(d / p));
|
|
628
|
-
u((B) => B && B.cols === E && B.rows === v ? B : { cols: E, rows: v });
|
|
629
|
-
}, y = f.getBoundingClientRect();
|
|
630
|
-
y.width > 0 && y.height > 0 && b(y.width, y.height);
|
|
631
|
-
const _ = new ResizeObserver(([g]) => {
|
|
632
|
-
const { width: d, height: m } = g.contentRect;
|
|
633
|
-
d > 0 && m > 0 && b(d, m);
|
|
634
|
-
});
|
|
635
|
-
return _.observe(f), () => _.disconnect();
|
|
636
|
-
}, [t.fit, t.terminal.fontSize, t.terminal.fontFamily]), N(() => {
|
|
637
|
-
if (!l || !r.current) return;
|
|
638
|
-
let f = !1, w = null;
|
|
639
|
-
const b = r.current, y = l, _ = (m) => {
|
|
640
|
-
var p;
|
|
641
|
-
i(m), (p = t.onStatusChange) == null || p.call(t, m);
|
|
642
|
-
}, g = (m) => {
|
|
643
|
-
var p;
|
|
644
|
-
_("error"), a(m instanceof Error ? m.message : String(m)), (p = t.onError) == null || p.call(t, m);
|
|
645
|
-
};
|
|
646
|
-
_("loading"), a("");
|
|
647
|
-
async function d() {
|
|
648
|
-
try {
|
|
649
|
-
let m = y.cols, p = y.rows, E = null;
|
|
650
|
-
const v = await _t({
|
|
651
|
-
container: b,
|
|
652
|
-
cols: y.cols,
|
|
653
|
-
rows: y.rows,
|
|
654
|
-
fontSize: t.terminal.fontSize,
|
|
655
|
-
fontFamily: t.terminal.fontFamily,
|
|
656
|
-
theme: t.terminal.theme,
|
|
657
|
-
convertEol: t.terminal.convertEol,
|
|
658
|
-
interactive: t.mode !== "static" && t.interactive,
|
|
659
|
-
showCursor: t.mode !== "static",
|
|
660
|
-
wasmUrl: t.terminal.wasmUrl,
|
|
661
|
-
onInput: (U) => E == null ? void 0 : E.pushInput(U)
|
|
662
|
-
});
|
|
663
|
-
if (f) {
|
|
664
|
-
v.dispose();
|
|
665
|
-
return;
|
|
666
|
-
}
|
|
667
|
-
w = () => v.dispose(), m = v.cols, p = v.rows, c.current = v.cellSize;
|
|
668
|
-
const M = t.resolveArgv({ cols: m, rows: p }), k = new TextDecoder(), B = new TextDecoder(), G = (U, I) => {
|
|
669
|
-
const V = I.decode(U, { stream: !0 });
|
|
670
|
-
V && v.write(V);
|
|
671
|
-
for (const st of v.drainResponses())
|
|
672
|
-
E == null || E.pushInput(st);
|
|
673
|
-
};
|
|
674
|
-
E = new zt({
|
|
675
|
-
args: [t.wasm.toString(), ...M],
|
|
676
|
-
env: t.env,
|
|
677
|
-
stdout: (U) => G(U, k),
|
|
678
|
-
stderr: (U) => G(U, B),
|
|
679
|
-
onExit: (U) => {
|
|
680
|
-
var I;
|
|
681
|
-
f || (_("exited"), (I = t.onExit) == null || I.call(t, U));
|
|
682
|
-
}
|
|
683
|
-
});
|
|
684
|
-
const rt = await Lt(t.wasm, E);
|
|
685
|
-
if (f) return;
|
|
686
|
-
_("running"), queueMicrotask(() => {
|
|
687
|
-
f || rt.run().catch((U) => {
|
|
688
|
-
f || g(U);
|
|
689
|
-
});
|
|
690
|
-
});
|
|
691
|
-
} catch (m) {
|
|
692
|
-
f || g(m);
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
return d(), () => {
|
|
696
|
-
f = !0, w == null || w(), w = null;
|
|
697
|
-
};
|
|
698
|
-
}, [
|
|
699
|
-
t.mode,
|
|
700
|
-
l,
|
|
701
|
-
t.wasm,
|
|
702
|
-
t.resolveArgv,
|
|
703
|
-
t.env,
|
|
704
|
-
t.fit,
|
|
705
|
-
t.interactive,
|
|
706
|
-
t.onExit,
|
|
707
|
-
t.onError,
|
|
708
|
-
t.onStatusChange,
|
|
709
|
-
t.terminal.fontSize,
|
|
710
|
-
t.terminal.fontFamily,
|
|
711
|
-
t.terminal.theme,
|
|
712
|
-
t.terminal.wasmUrl,
|
|
713
|
-
t.terminal.convertEol
|
|
714
|
-
]), /* @__PURE__ */ O(
|
|
715
|
-
"div",
|
|
716
|
-
{
|
|
717
|
-
ref: n,
|
|
718
|
-
className: e.className,
|
|
719
|
-
style: {
|
|
720
|
-
position: "relative",
|
|
721
|
-
display: t.fit === "container" ? "block" : "inline-block",
|
|
722
|
-
background: ((h = t.terminal.theme) == null ? void 0 : h.background) ?? "#1a1b26",
|
|
723
|
-
borderRadius: 6,
|
|
724
|
-
overflow: "hidden",
|
|
725
|
-
...e.style
|
|
726
|
-
},
|
|
727
|
-
children: [
|
|
728
|
-
/* @__PURE__ */ H("div", { ref: r, style: { display: o === "error" ? "none" : void 0 } }),
|
|
729
|
-
o === "loading" && /* @__PURE__ */ H("div", { style: J, children: "Loading…" }),
|
|
730
|
-
o === "error" && /* @__PURE__ */ O("div", { style: { ...J, color: "#f7768e" }, children: [
|
|
731
|
-
"Error: ",
|
|
732
|
-
s
|
|
733
|
-
] })
|
|
734
|
-
]
|
|
735
|
-
}
|
|
736
|
-
);
|
|
737
|
-
}
|
|
738
|
-
const J = {
|
|
739
|
-
padding: "1rem",
|
|
740
|
-
fontFamily: "monospace",
|
|
741
|
-
fontSize: 14,
|
|
742
|
-
color: "#a9b1d6"
|
|
743
|
-
};
|
|
744
|
-
export {
|
|
745
|
-
$t as TuiPreview,
|
|
746
|
-
zt as WasiBridge,
|
|
747
|
-
nt as WasiExitError,
|
|
748
|
-
Lt as instantiateApp
|
|
749
|
-
};
|
|
1
|
+
export { TuiPreview } from "./TuiPreview.js";
|
|
2
|
+
export { WasiBridge, WasiExitError, instantiateApp } from "./core/wasi.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dkkoval/tui-preview",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "React component for embedding interactive TUI apps via libghostty",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -13,14 +13,12 @@
|
|
|
13
13
|
"type": "module",
|
|
14
14
|
"license": "MIT",
|
|
15
15
|
"sideEffects": false,
|
|
16
|
-
"main": "./dist/index.cjs",
|
|
17
16
|
"module": "./dist/index.js",
|
|
18
17
|
"types": "./dist/index.d.ts",
|
|
19
18
|
"exports": {
|
|
20
19
|
".": {
|
|
21
20
|
"types": "./dist/index.d.ts",
|
|
22
|
-
"import": "./dist/index.js"
|
|
23
|
-
"require": "./dist/index.cjs"
|
|
21
|
+
"import": "./dist/index.js"
|
|
24
22
|
},
|
|
25
23
|
"./core": {
|
|
26
24
|
"types": "./dist/core/index.d.ts",
|
|
@@ -34,12 +32,13 @@
|
|
|
34
32
|
"clean": "rm -rf dist dist-example",
|
|
35
33
|
"build:ghostty-wasm": "./scripts/build-libghostty-wasm.sh",
|
|
36
34
|
"dev": "vite example",
|
|
35
|
+
"build:examples": "./scripts/build-examples-wasm.sh",
|
|
37
36
|
"build": "vite build",
|
|
38
|
-
"build:lib": "tsc -p tsconfig.json
|
|
39
|
-
"prepack": "tsc -p tsconfig.json &&
|
|
37
|
+
"build:lib": "tsc -p tsconfig.json",
|
|
38
|
+
"prepack": "tsc -p tsconfig.json && ./scripts/build-libghostty-wasm.sh dist/core/ghostty-vt.wasm",
|
|
40
39
|
"typecheck": "tsc --noEmit",
|
|
41
|
-
"test": "tsc -p tsconfig.json &&
|
|
42
|
-
"check": "tsc --noEmit && tsc -p tsconfig.json &&
|
|
40
|
+
"test": "tsc -p tsconfig.json && node --test tests/*.test.mjs",
|
|
41
|
+
"check": "tsc --noEmit && tsc -p tsconfig.json && node --test tests/*.test.mjs"
|
|
43
42
|
},
|
|
44
43
|
"peerDependencies": {
|
|
45
44
|
"react": ">=18",
|
package/dist/index.cjs
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
"use strict";var st=Object.defineProperty;var it=(e,t,n)=>t in e?st(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var B=(e,t,n)=>it(e,typeof t!="symbol"?t+"":t,n);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const L=require("react/jsx-runtime"),U=require("react"),F=16,M=80,Z="/ghostty-vt.wasm",ot=1,at=2,ct=4,W=8,O=16,lt=32,$=64,H=128,ht=["black","red","green","yellow","blue","magenta","cyan","white","brightBlack","brightRed","brightGreen","brightYellow","brightBlue","brightMagenta","brightCyan","brightWhite"],ut={background:"#1a1b26",foreground:"#a9b1d6",cursor:"#c0caf5",selectionBackground:"#33467c",selectionForeground:"#c0caf5",black:"#15161e",red:"#f7768e",green:"#9ece6a",yellow:"#e0af68",blue:"#7aa2f7",magenta:"#bb9af7",cyan:"#7dcfff",white:"#a9b1d6",brightBlack:"#414868",brightRed:"#f7768e",brightGreen:"#9ece6a",brightYellow:"#e0af68",brightBlue:"#7aa2f7",brightMagenta:"#bb9af7",brightCyan:"#7dcfff",brightWhite:"#c0caf5"};class ft{constructor(t,n){this.wasm=t,this.abi=n}createTerminal(t,n,r){const o=this.wasm.ghostty_wasm_alloc_u8_array(this.abi.terminalConfigSize);if(!o)throw new Error("Failed to allocate terminal config.");try{const i=new DataView(this.wasm.memory.buffer);let s=o;i.setUint32(s,1e4,!0),s+=4,i.setUint32(s,D(r.foreground),!0),s+=4,i.setUint32(s,D(r.background),!0),s+=4,i.setUint32(s,D(r.cursor),!0),s+=4;for(const c of ht)i.setUint32(s,D(r[c]),!0),s+=4;const a=this.wasm.ghostty_terminal_new_with_config(t,n,o);if(!a)throw new Error("Failed to create libghostty terminal.");return new wt(this.wasm,a,t,n,this.abi.cellSize)}finally{this.wasm.ghostty_wasm_free_u8_array(o,this.abi.terminalConfigSize)}}}class wt{constructor(t,n,r,o,i){B(this,"viewportPtr",0);B(this,"viewportLen",0);this.wasm=t,this.handle=n,this.cols=r,this.rows=o,this.cellSize=i}write(t){const n=typeof t=="string"?new TextEncoder().encode(t):t;if(n.length===0)return;const r=this.wasm.ghostty_wasm_alloc_u8_array(n.length);if(!r)throw new Error("Failed to allocate libghostty write buffer.");try{new Uint8Array(this.wasm.memory.buffer).set(n,r),this.wasm.ghostty_terminal_write(this.handle,r,n.length)}finally{this.wasm.ghostty_wasm_free_u8_array(r,n.length)}}resize(t,n){t===this.cols&&n===this.rows||(this.cols=t,this.rows=n,this.wasm.ghostty_terminal_resize(this.handle,t,n),this.releaseViewport())}hasResponse(){return this.wasm.ghostty_terminal_has_response(this.handle)}isDirty(){return this.wasm.ghostty_render_state_update(this.handle)!==0}readResponse(t=4096){const n=this.wasm.ghostty_wasm_alloc_u8_array(t);if(!n)throw new Error("Failed to allocate libghostty response buffer.");try{const r=this.wasm.ghostty_terminal_read_response(this.handle,n,t);if(r<=0)return null;const o=new Uint8Array(this.wasm.memory.buffer,n,r);return new TextDecoder().decode(o)}finally{this.wasm.ghostty_wasm_free_u8_array(n,t)}}getViewportData(){const t=this.wasm.ghostty_render_state_get_cols(this.handle),n=this.wasm.ghostty_render_state_get_rows(this.handle);this.cols=t,this.rows=n;const r=Math.max(1,t*n*this.cellSize);if((r>this.viewportLen||this.viewportPtr===0)&&(this.releaseViewport(),this.viewportPtr=this.wasm.ghostty_wasm_alloc_u8_array(r),this.viewportLen=r,!this.viewportPtr))throw new Error("Failed to allocate libghostty viewport buffer.");const o=this.wasm.ghostty_render_state_get_viewport(this.handle,this.viewportPtr,this.viewportLen),i=new Uint8Array(o);if(o>0){const s=new Uint8Array(this.wasm.memory.buffer,this.viewportPtr,o);i.set(s)}return this.wasm.ghostty_render_state_mark_clean(this.handle),{cols:t,rows:n,buffer:i}}dispose(){this.releaseViewport(),this.wasm.ghostty_terminal_free(this.handle)}releaseViewport(){this.viewportPtr!==0&&(this.wasm.ghostty_wasm_free_u8_array(this.viewportPtr,this.viewportLen),this.viewportPtr=0,this.viewportLen=0)}}class mt{constructor(t,n,r,o,i,s){B(this,"ctx");B(this,"dpr");B(this,"metrics");this.canvas=t,this.cols=n,this.rows=r,this.fontSize=o,this.fontFamily=i,this.theme=s;const a=t.getContext("2d");if(!a)throw new Error("Failed to create 2D canvas context.");this.ctx=a,this.dpr=window.devicePixelRatio||1,this.metrics=this.measureFont(),this.resizeCanvas(n,r)}get cellSize(){return{w:this.metrics.width,h:this.metrics.height}}render(t){(t.cols!==this.cols||t.rows!==this.rows)&&(this.cols=t.cols,this.rows=t.rows,this.resizeCanvas(t.cols,t.rows));const{cols:n,rows:r,buffer:o}=t,i=new DataView(o.buffer,o.byteOffset,o.byteLength),s=this.ctx,a=this.metrics.width,c=this.metrics.height;s.fillStyle=this.theme.background,s.fillRect(0,0,n*a,r*c);for(let l=0;l<r;l++)for(let u=0;u<n;u++){const h=(l*n+u)*F,f=i.getUint8(h+11);if(f===0)continue;const w=i.getUint8(h+10),b=R(i,h+4),y=R(i,h+7),_=x(w,O),g=x(w,W)?P(b):this.theme.foreground,d=x(w,$)?P(y):this.theme.background;(_||x(w,$))&&(s.fillStyle=_?g:d,s.fillRect(u*a,l*c,f*a,c))}for(let l=0;l<r;l++)for(let u=0;u<n;u++){const h=(l*n+u)*F,f=i.getUint8(h+11);if(f===0)continue;const w=i.getUint8(h+10);if(x(w,lt))continue;const b=i.getUint32(h,!0);if(b===0)continue;const y=x(w,O),_=R(i,h+4),g=R(i,h+7),d=x(w,W)?P(_):this.theme.foreground,m=x(w,$)?P(g):this.theme.background;s.fillStyle=y?m:d;let p="";x(w,at)&&(p+="italic "),x(w,ot)&&(p+="bold "),s.font=`${p}${this.fontSize}px ${this.fontFamily}`,x(w,H)&&(s.globalAlpha=.5);const E=bt(b);if(s.fillText(E,u*a,l*c+this.metrics.baseline),x(w,H)&&(s.globalAlpha=1),x(w,ct)){const v=l*c+this.metrics.baseline+2;s.strokeStyle=s.fillStyle,s.lineWidth=1,s.beginPath(),s.moveTo(u*a,v),s.lineTo(u*a+f*a,v),s.stroke()}}}dispose(){this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height)}measureFont(){this.ctx.font=`${this.fontSize}px ${this.fontFamily}`;const t=this.ctx.measureText("M"),n=Math.ceil(t.width),r=t.actualBoundingBoxAscent||this.fontSize*.8,o=t.actualBoundingBoxDescent||this.fontSize*.2;return{width:n,height:Math.ceil(r+o)+2,baseline:Math.ceil(r)+1}}resizeCanvas(t,n){const r=t*this.metrics.width,o=n*this.metrics.height;this.canvas.width=Math.max(1,Math.floor(r*this.dpr)),this.canvas.height=Math.max(1,Math.floor(o*this.dpr)),this.canvas.style.width=`${r}px`,this.canvas.style.height=`${o}px`,this.ctx.setTransform(this.dpr,0,0,this.dpr,0,0),this.ctx.textBaseline="alphabetic",this.ctx.textAlign="left"}}function J(e,t){const r=document.createElement("canvas").getContext("2d");if(!r)return{w:Math.ceil(e*.6),h:Math.ceil(e*1.2)};r.font=`${e}px ${t}`;const o=r.measureText("M"),i=Math.ceil(o.width),s=o.actualBoundingBoxAscent||e*.8,a=o.actualBoundingBoxDescent||e*.2;return{w:i,h:Math.ceil(s+a)+2}}const K=new Map;function dt(e=Z){const t=e.toString(),n=K.get(t);if(n)return n;const r=(async()=>{const o=await fetch(e);if(!o.ok)throw new Error(`Failed to load libghostty wasm: ${o.status} ${o.statusText}`);const i=await o.arrayBuffer();if(i.byteLength===0)throw new Error("libghostty wasm is empty.");const s=await WebAssembly.compile(i),c=(await WebAssembly.instantiate(s,{env:{log:()=>{}}})).exports;if(!c.memory||!c.ghostty_terminal_new||!c.ghostty_render_state_get_viewport)throw new Error("Invalid libghostty wasm exports.");return yt(c),new ft(c,{cellSize:F,terminalConfigSize:M})})();return K.set(t,r),r}async function gt(e){var _,g;const t=await dt(e.wasmUrl??Z),n={...ut,...e.theme};let r,o;const i=J(e.fontSize,e.fontFamily);e.widthPx!=null&&e.heightPx!=null?(r=Math.max(1,Math.floor(e.widthPx/i.w)),o=Math.max(1,Math.floor(e.heightPx/i.h))):(r=e.cols??80,o=e.rows??24),e.container.innerHTML="";const s=document.createElement("canvas");s.style.display="block",s.style.outline="none",s.tabIndex=e.interactive?0:-1,e.container.appendChild(s);const a=t.createTerminal(r,o,n),c=new mt(s,r,o,e.fontSize,e.fontFamily,n);e.showCursor||a.write("\x1B[?25l"),c.render(a.getViewportData());const l=((_=window.requestAnimationFrame)==null?void 0:_.bind(window))??(d=>window.setTimeout(d,16)),u=((g=window.cancelAnimationFrame)==null?void 0:g.bind(window))??window.clearTimeout.bind(window);let h=null,f=!1;const w=()=>{h=null,!(f||!a.isDirty())&&c.render(a.getViewportData())},b=()=>{h!==null||f||(h=l(w))},y=e.interactive?pt(s,d=>{var m;return(m=e.onInput)==null?void 0:m.call(e,d)}):()=>{};return{cols:r,rows:o,cellSize:c.cellSize,write(d){if(f)return;const m=e.convertEol?_t(d):d;a.write(m),b()},drainResponses(){if(f)return[];const d=[];for(;a.hasResponse();){const m=a.readResponse();if(!m)break;d.push(m)}return d},dispose(){f=!0,h!==null&&(u(h),h=null),y(),c.dispose(),a.dispose(),s.parentElement===e.container&&e.container.removeChild(s)}}}function yt(e){const t=e.ghostty_wasm_alloc_u8_array(M);if(!t)throw new Error("Failed to allocate ABI probe config buffer.");let n=0,r=0,o=0,i=0;const s=1122867,a=4478310,[c,l,u]=j(s),[h,f,w]=j(a);try{new Uint8Array(e.memory.buffer,t,M).fill(0);const b=new DataView(e.memory.buffer,t,M);if(b.setUint32(0,16,!0),b.setUint32(4,s,!0),b.setUint32(8,a,!0),b.setUint32(12,7833753,!0),n=e.ghostty_terminal_new_with_config(2,1,t),!n)throw new Error("Failed to create ABI probe terminal.");const y=new TextEncoder().encode("\x1B[7mX");if(o=y.length,r=e.ghostty_wasm_alloc_u8_array(o),!r)throw new Error("Failed to allocate ABI probe write buffer.");if(new Uint8Array(e.memory.buffer,r,o).set(y),e.ghostty_terminal_write(n,r,o),i=e.ghostty_wasm_alloc_u8_array(F),!i)throw new Error("Failed to allocate ABI probe viewport buffer.");const _=e.ghostty_render_state_get_viewport(n,i,F);if(_!==F)throw new Error(`Incompatible libghostty ABI: expected cell size ${F}, got ${_}.`);const g=new DataView(e.memory.buffer,i,F),d=g.getUint8(10),m=x(d,W),p=x(d,$),E=g.getUint8(4)===c&&g.getUint8(5)===l&&g.getUint8(6)===u,v=g.getUint8(7)===h&&g.getUint8(8)===f&&g.getUint8(9)===w;if(!m||!p||!E||!v)throw new Error("Incompatible libghostty ABI: terminal config layout mismatch.")}finally{i&&e.ghostty_wasm_free_u8_array(i,F),r&&o>0&&e.ghostty_wasm_free_u8_array(r,o),n&&e.ghostty_terminal_free(n),e.ghostty_wasm_free_u8_array(t,M)}}function _t(e){return e.replace(/\r?\n/g,`\r
|
|
2
|
-
`)}function D(e){if(e.startsWith("#")){let i=e.slice(1);i.length===3&&(i=`${i[0]}${i[0]}${i[1]}${i[1]}${i[2]}${i[2]}`);const s=Number.parseInt(i,16);return Number.isNaN(s)?0:s}const t=e.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);if(!t)return 0;const n=Number.parseInt(t[1],10),r=Number.parseInt(t[2],10),o=Number.parseInt(t[3],10);return n<<16|r<<8|o}function j(e){return[e>>16&255,e>>8&255,e&255]}function R(e,t){return{r:e.getUint8(t),g:e.getUint8(t+1),b:e.getUint8(t+2)}}function x(e,t){return(e&t)!==0}function P(e){return`rgb(${e.r}, ${e.g}, ${e.b})`}function bt(e){try{return String.fromCodePoint(e)}catch{return" "}}function pt(e,t){const n=()=>e.focus(),r=i=>{var a;const s=(a=i.clipboardData)==null?void 0:a.getData("text");s&&(i.preventDefault(),t(s))},o=i=>{const s=Et(i);s&&(i.preventDefault(),t(s))};return e.addEventListener("mousedown",n),e.addEventListener("paste",r),e.addEventListener("keydown",o),()=>{e.removeEventListener("mousedown",n),e.removeEventListener("paste",r),e.removeEventListener("keydown",o)}}function Et(e){if(e.isComposing||e.metaKey)return null;let t=null;switch(e.key){case"Enter":t="\r";break;case"Backspace":t="";break;case"Tab":t=e.shiftKey?"\x1B[Z":" ";break;case"Escape":t="\x1B";break;case"ArrowUp":t="\x1B[A";break;case"ArrowDown":t="\x1B[B";break;case"ArrowRight":t="\x1B[C";break;case"ArrowLeft":t="\x1B[D";break;case"Home":t="\x1B[H";break;case"End":t="\x1B[F";break;case"Delete":t="\x1B[3~";break;case"PageUp":t="\x1B[5~";break;case"PageDown":t="\x1B[6~";break}return!t&&e.ctrlKey&&(t=vt(e.key)),!t&&e.key.length===1&&!e.ctrlKey&&(t=e.key),t?e.altKey&&!t.startsWith("\x1B")?`\x1B${t}`:t:null}function vt(e){if(e.length!==1)return null;const t=e.toUpperCase();if(t>="A"&&t<="Z")return String.fromCharCode(t.charCodeAt(0)-64);switch(e){case"@":case" ":return"\0";case"[":return"\x1B";case"\\":return"";case"]":return"";case"^":return"";case"_":return"";default:return null}}const Y={cols:80,rows:24},xt={},At=[];function St(e){return e==="static"?"static":"interactive"}function Ut(e){const t=e??At;return typeof t=="function"?t:()=>t}function Ft(e){var o,i,s,a,c,l,u;const t=e.fit??(e.size?"none":"container"),n={cols:Math.max(1,((o=e.size)==null?void 0:o.cols)??Y.cols),rows:Math.max(1,((i=e.size)==null?void 0:i.rows)??Y.rows)},r=St(e.mode);return{wasm:e.wasm,env:e.env??xt,interactive:r==="static"?!1:e.interactive??!0,mode:r,fit:t,size:n,terminal:{fontSize:((s=e.terminal)==null?void 0:s.fontSize)??14,fontFamily:((a=e.terminal)==null?void 0:a.fontFamily)??"monospace",wasmUrl:(c=e.terminal)==null?void 0:c.wasmUrl,convertEol:((l=e.terminal)==null?void 0:l.convertEol)??!0,theme:(u=e.terminal)==null?void 0:u.theme},resolveArgv:Ut(e.argv),onExit:e.onExit,onError:e.onError,onStatusChange:e.onStatusChange}}const A=0,Bt=6,C=8,Tt=1,Ct=0,q=1,Mt=2;class tt{constructor(t){B(this,"inputQueue",[]);B(this,"memory");this.opts=t}pushInput(t){const n=typeof t=="string"?new TextEncoder().encode(t):t;this.inputQueue.push(n)}attachMemory(t){this.memory=t}view(){return new DataView(this.memory.buffer)}u8(){return new Uint8Array(this.memory.buffer)}get imports(){return{args_sizes_get:(t,n)=>{const{args:r}=this.opts,o=new TextEncoder,i=r.reduce((s,a)=>s+o.encode(a).length+1,0);return this.view().setUint32(t,r.length,!0),this.view().setUint32(n,i,!0),A},args_get:(t,n)=>{const r=new TextEncoder,o=this.u8(),i=this.view();let s=n;return this.opts.args.forEach((a,c)=>{const l=r.encode(a);o.set(l,s),o[s+l.length]=0,i.setUint32(t+c*4,s,!0),s+=l.length+1}),A},environ_sizes_get:(t,n)=>{const r=this.envEntries(),o=new TextEncoder,i=r.reduce((s,a)=>s+o.encode(a).length+1,0);return this.view().setUint32(t,r.length,!0),this.view().setUint32(n,i,!0),A},environ_get:(t,n)=>{const r=new TextEncoder,o=this.u8(),i=this.view();let s=n;return this.envEntries().forEach((a,c)=>{const l=r.encode(a);o.set(l,s),o[s+l.length]=0,i.setUint32(t+c*4,s,!0),s+=l.length+1}),A},fd_write:(t,n,r,o)=>{if(t!==q&&t!==Mt)return C;const i=this.view(),s=this.u8();let a=0;const c=[];for(let h=0;h<r;h++){const f=i.getUint32(n+h*8,!0),w=i.getUint32(n+h*8+4,!0);c.push(s.slice(f,f+w)),a+=w}const l=new Uint8Array(a);let u=0;for(const h of c)l.set(h,u),u+=h.length;return t===q?this.opts.stdout(l):this.opts.stderr(l),i.setUint32(o,a,!0),A},fd_read:(t,n,r,o)=>{if(t!==Ct)return C;const i=this.inputQueue[0];if(!i)return Bt;const s=this.view(),a=this.u8();let c=0;for(let l=0;l<r&&c<i.length;l++){const u=s.getUint32(n+l*8,!0),h=s.getUint32(n+l*8+4,!0),f=Math.min(h,i.length-c);a.set(i.subarray(c,c+f),u),c+=f}return c>=i.length?this.inputQueue.shift():c>0&&(this.inputQueue[0]=i.subarray(c)),s.setUint32(o,c,!0),A},poll_oneoff:(t,n,r,o)=>{const i=this.view();let s=0;for(let a=0;a<r;a++){const c=t+a*48,l=i.getUint8(c+8);if(l===Tt&&this.inputQueue.length>0){const u=n+s*32;i.setBigUint64(u,i.getBigUint64(c,!0),!0),i.setUint16(u+8,0,!0),i.setUint8(u+10,l),s++}}return i.setUint32(o,s,!0),A},proc_exit:t=>{throw this.opts.onExit(t),new N(t)},random_get:(t,n)=>(crypto.getRandomValues(new Uint8Array(this.memory.buffer,t,n)),A),fd_close:()=>A,fd_seek:()=>A,fd_fdstat_get:(t,n)=>(this.view().setUint8(n,t<=2?2:0),A),fd_prestat_get:()=>C,fd_prestat_dir_name:()=>C,path_open:()=>C,sched_yield:()=>A,clock_time_get:(t,n,r)=>{const o=BigInt(Date.now())*1000000n;return this.view().setBigUint64(r,o,!0),A}}}envEntries(){const t={TERM:"xterm-256color",COLORTERM:"truecolor",...this.opts.env};return Object.entries(t).map(([n,r])=>`${n}=${r}`)}}class N extends Error{constructor(t){super(`WASI exit: ${t}`),this.code=t}}const Q=new Map;async function et(e,t){const n=e.toString();let r=Q.get(n);if(!r){const a=await fetch(e);if(!a.ok)throw new Error(`Failed to load app wasm: ${a.status} ${a.statusText}`);const c=await a.arrayBuffer();if(c.byteLength===0)throw new Error("App wasm is empty.");r=await WebAssembly.compile(c),Q.set(n,r)}const o={wasi_snapshot_preview1:t.imports},i=await WebAssembly.instantiate(r,o);t.attachMemory(i.exports.memory);const s=i.exports._start;if(!s)throw new Error("WASM module has no _start export");return{run:async()=>{try{s()}catch(a){if(!(a instanceof N))throw a}}}}function kt(e){var h;const t=U.useMemo(()=>Ft(e),[e]),n=U.useRef(null),r=U.useRef(null),[o,i]=U.useState("loading"),[s,a]=U.useState(""),c=U.useRef(null),[l,u]=U.useState(t.fit==="container"?null:t.size);return U.useEffect(()=>{u(t.fit==="container"?null:t.size)},[t.fit,t.size.cols,t.size.rows]),U.useEffect(()=>{if(t.fit!=="container"||!n.current)return;const f=n.current,w=J(t.terminal.fontSize,t.terminal.fontFamily),b=(g,d)=>{var k,I;const m=((k=c.current)==null?void 0:k.w)??w.w,p=((I=c.current)==null?void 0:I.h)??w.h,E=Math.max(1,Math.floor(g/m)),v=Math.max(1,Math.floor(d/p));u(T=>T&&T.cols===E&&T.rows===v?T:{cols:E,rows:v})},y=f.getBoundingClientRect();y.width>0&&y.height>0&&b(y.width,y.height);const _=new ResizeObserver(([g])=>{const{width:d,height:m}=g.contentRect;d>0&&m>0&&b(d,m)});return _.observe(f),()=>_.disconnect()},[t.fit,t.terminal.fontSize,t.terminal.fontFamily]),U.useEffect(()=>{if(!l||!r.current)return;let f=!1,w=null;const b=r.current,y=l,_=m=>{var p;i(m),(p=t.onStatusChange)==null||p.call(t,m)},g=m=>{var p;_("error"),a(m instanceof Error?m.message:String(m)),(p=t.onError)==null||p.call(t,m)};_("loading"),a("");async function d(){try{let m=y.cols,p=y.rows,E=null;const v=await gt({container:b,cols:y.cols,rows:y.rows,fontSize:t.terminal.fontSize,fontFamily:t.terminal.fontFamily,theme:t.terminal.theme,convertEol:t.terminal.convertEol,interactive:t.mode!=="static"&&t.interactive,showCursor:t.mode!=="static",wasmUrl:t.terminal.wasmUrl,onInput:S=>E==null?void 0:E.pushInput(S)});if(f){v.dispose();return}w=()=>v.dispose(),m=v.cols,p=v.rows,c.current=v.cellSize;const k=t.resolveArgv({cols:m,rows:p}),I=new TextDecoder,T=new TextDecoder,G=(S,z)=>{const V=z.decode(S,{stream:!0});V&&v.write(V);for(const rt of v.drainResponses())E==null||E.pushInput(rt)};E=new tt({args:[t.wasm.toString(),...k],env:t.env,stdout:S=>G(S,I),stderr:S=>G(S,T),onExit:S=>{var z;f||(_("exited"),(z=t.onExit)==null||z.call(t,S))}});const nt=await et(t.wasm,E);if(f)return;_("running"),queueMicrotask(()=>{f||nt.run().catch(S=>{f||g(S)})})}catch(m){f||g(m)}}return d(),()=>{f=!0,w==null||w(),w=null}},[t.mode,l,t.wasm,t.resolveArgv,t.env,t.fit,t.interactive,t.onExit,t.onError,t.onStatusChange,t.terminal.fontSize,t.terminal.fontFamily,t.terminal.theme,t.terminal.wasmUrl,t.terminal.convertEol]),L.jsxs("div",{ref:n,className:e.className,style:{position:"relative",display:t.fit==="container"?"block":"inline-block",background:((h=t.terminal.theme)==null?void 0:h.background)??"#1a1b26",borderRadius:6,overflow:"hidden",...e.style},children:[L.jsx("div",{ref:r,style:{display:o==="error"?"none":void 0}}),o==="loading"&&L.jsx("div",{style:X,children:"Loading…"}),o==="error"&&L.jsxs("div",{style:{...X,color:"#f7768e"},children:["Error: ",s]})]})}const X={padding:"1rem",fontFamily:"monospace",fontSize:14,color:"#a9b1d6"};exports.TuiPreview=kt;exports.WasiBridge=tt;exports.WasiExitError=N;exports.instantiateApp=et;
|