@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.
@@ -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
- let bridge = null;
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) => bridge?.pushInput(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 wasmApp.run().catch((runError) => {
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: { display: status === "error" ? "none" : undefined } }), status === "loading" && (_jsx("div", { style: overlayStyle, children: "Loading\u2026" })), status === "error" && (_jsxs("div", { style: { ...overlayStyle, color: "#f7768e" }, children: ["Error: ", errorMsg] }))] }));
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",
@@ -1,6 +1,6 @@
1
1
  const EXPECTED_CELL_SIZE = 16;
2
2
  const EXPECTED_TERMINAL_CONFIG_SIZE = 80;
3
- const DEFAULT_WASM_URL = "/ghostty-vt.wasm";
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;
@@ -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 (non-blocking stdin check, needed by crossterm/ratatui)
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 (non-blocking stdin check, needed by crossterm/ratatui)
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: (inPtr, outPtr, nsubscriptions, neventsPtr) => {
142
- const view = this.view();
143
- let nevents = 0;
144
- for (let i = 0; i < nsubscriptions; i++) {
145
- const subPtr = inPtr + i * 48;
146
- const type = view.getUint8(subPtr + 8);
147
- if (type === WASI_EVENTTYPE_FD_READ && this.inputQueue.length > 0) {
148
- const evPtr = outPtr + nevents * 32;
149
- view.setBigUint64(evPtr, view.getBigUint64(subPtr, true), true);
150
- view.setUint16(evPtr + 8, 0, true);
151
- view.setUint8(evPtr + 10, type);
152
- nevents++;
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
- view.setUint32(neventsPtr, nevents, true);
156
- return WASI_ESUCCESS;
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: bridge.imports,
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 _start = instance.exports._start;
225
- if (!_start)
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
- var it = Object.defineProperty;
2
- var ot = (e, t, n) => t in e ? it(e, t, { enumerable: !0, configurable: !0, writable: !0, value: n }) : e[t] = n;
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.0",
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 && vite build --mode lib",
39
- "prepack": "tsc -p tsconfig.json && vite build --mode lib && ./scripts/build-libghostty-wasm.sh dist/ghostty-vt.wasm",
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 && vite build --mode lib && node --test tests/*.test.mjs",
42
- "check": "tsc --noEmit && tsc -p tsconfig.json && vite build --mode lib && node --test tests/*.test.mjs"
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;