@dkkoval/tui-preview 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -16
- package/dist/TuiPreview.js +64 -145
- package/dist/core/index.d.ts +2 -3
- package/dist/core/index.js +2 -3
- package/dist/core/libghostty.d.ts +86 -0
- package/dist/core/libghostty.js +678 -0
- package/dist/core/normalize.d.ts +0 -1
- package/dist/core/normalize.js +20 -66
- package/dist/core/wasi.d.ts +1 -1
- package/dist/core/wasi.js +26 -6
- package/dist/ghostty-vt.wasm +0 -0
- package/dist/index.cjs +2 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +645 -411
- package/dist/types.d.ts +8 -26
- package/package.json +14 -7
- package/dist/__vite-browser-external-2447137e-BcPniuRQ.cjs +0 -1
- package/dist/__vite-browser-external-2447137e-DYxpcVy9.js +0 -4
- package/dist/core/ansi.d.ts +0 -15
- package/dist/core/ansi.js +0 -181
- package/dist/core/ghostty.d.ts +0 -2
- package/dist/core/ghostty.js +0 -11
- package/dist/ghostty-web-BfBVpf8G.js +0 -2962
- package/dist/ghostty-web-DkOZu5AZ.cjs +0 -13
- package/dist/wasi.d.ts +0 -1
- package/dist/wasi.js +0 -2
package/README.md
CHANGED
|
@@ -5,13 +5,27 @@ Render `wasm32-wasi` terminal apps inside React with a clean, size-aware API.
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install tui-preview
|
|
8
|
+
npm install @dkkoval/tui-preview
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
Build libghostty wasm from the submodule before running the example:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm run build:ghostty-wasm
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
This builds an external wasm wrapper (`wasm/ghostty-vt/build.zig` + `wasm/ghostty-vt/wrapper.zig`)
|
|
18
|
+
that imports Ghostty sources from `vendor/libghostty` without modifying submodule files.
|
|
19
|
+
The wrapper is intentionally minimal: ANSI parsing + viewport rendering + basic input responses.
|
|
20
|
+
|
|
21
|
+
By default this writes:
|
|
22
|
+
- `example/public/ghostty-vt.wasm` (dev/example)
|
|
23
|
+
- `dist/ghostty-vt.wasm` (library package asset)
|
|
24
|
+
|
|
11
25
|
## Modern API (v1)
|
|
12
26
|
|
|
13
27
|
```tsx
|
|
14
|
-
import { TuiPreview } from "tui-preview";
|
|
28
|
+
import { TuiPreview } from "@dkkoval/tui-preview";
|
|
15
29
|
|
|
16
30
|
function Demo() {
|
|
17
31
|
return (
|
|
@@ -48,31 +62,25 @@ function Demo() {
|
|
|
48
62
|
- `argv?: string[] | ((size) => string[])`
|
|
49
63
|
- CLI args (without argv[0]).
|
|
50
64
|
- For `fit="container"`, size is the fitted terminal size.
|
|
65
|
+
- `mode?: "interactive" | "static"` (default: `"interactive"`)
|
|
66
|
+
- `"interactive"`: keyboard/mouse-enabled terminal surface.
|
|
67
|
+
- `"static"`: non-interactive render surface.
|
|
51
68
|
- `fit?: "container" | "none"` (default: `"container"`)
|
|
52
69
|
- `"container"`: auto-size from container.
|
|
53
70
|
- `"none"`: fixed terminal size from `size`.
|
|
54
71
|
- `size?: { cols: number; rows: number }`
|
|
55
72
|
- Required in practice for fixed mode; fallback/initial for container mode.
|
|
56
|
-
- `terminal?: { fontSize, fontFamily, theme,
|
|
73
|
+
- `terminal?: { fontSize, fontFamily, theme, convertEol }`
|
|
74
|
+
- `terminal.wasmUrl?: string | URL` (default: `"/ghostty-vt.wasm"`)
|
|
57
75
|
- `interactive?: boolean` (default: `true`)
|
|
58
76
|
- `env?: Record<string, string>`
|
|
59
77
|
- `onExit?: (code: number) => void`
|
|
60
78
|
- `onError?: (error: unknown) => void`
|
|
61
79
|
- `onStatusChange?: ("loading" | "running" | "exited" | "error") => void`
|
|
62
80
|
|
|
63
|
-
## Legacy Compatibility
|
|
64
|
-
|
|
65
|
-
Legacy props still work:
|
|
66
|
-
|
|
67
|
-
- `app`, `args`
|
|
68
|
-
- `cols`, `rows`
|
|
69
|
-
- `fontSize`, `fontFamily`, `theme`
|
|
70
|
-
|
|
71
|
-
They are translated internally to the modern API and emit a one-time deprecation warning.
|
|
72
|
-
|
|
73
81
|
## Notes
|
|
74
82
|
|
|
75
83
|
- Package exports:
|
|
76
|
-
-
|
|
77
|
-
-
|
|
78
|
-
-
|
|
84
|
+
- `@dkkoval/tui-preview` (React component + public types)
|
|
85
|
+
- `@dkkoval/tui-preview/core` (advanced internals)
|
|
86
|
+
- libghostty source is tracked as a git submodule at `vendor/libghostty`.
|
package/dist/TuiPreview.js
CHANGED
|
@@ -1,55 +1,58 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { resolveTuiPreviewProps, warnLegacyPropsOnce } from "./core/normalize.js";
|
|
3
|
+
import { createMiniTerminalSurface, measureCellSize } from "./core/libghostty.js";
|
|
4
|
+
import { resolveTuiPreviewProps } from "./core/normalize.js";
|
|
6
5
|
import { WasiBridge, instantiateApp } from "./core/wasi.js";
|
|
7
6
|
export function TuiPreview(props) {
|
|
8
7
|
const resolved = useMemo(() => resolveTuiPreviewProps(props), [props]);
|
|
9
8
|
const wrapperRef = useRef(null);
|
|
10
9
|
const containerRef = useRef(null);
|
|
11
|
-
const termRef = useRef(null);
|
|
12
10
|
const [status, setStatus] = useState("loading");
|
|
13
11
|
const [errorMsg, setErrorMsg] = useState("");
|
|
14
12
|
const cellSizeRef = useRef(null);
|
|
15
|
-
const [termSize, setTermSize] = useState(resolved.size);
|
|
16
|
-
const [staticHtml, setStaticHtml] = useState(null);
|
|
13
|
+
const [termSize, setTermSize] = useState(resolved.fit === "container" ? null : resolved.size);
|
|
17
14
|
useEffect(() => {
|
|
18
|
-
|
|
19
|
-
}, [resolved.
|
|
20
|
-
// ── Terminal mode effects ────────────────────────────────────────────────
|
|
15
|
+
setTermSize(resolved.fit === "container" ? null : resolved.size);
|
|
16
|
+
}, [resolved.fit, resolved.size.cols, resolved.size.rows]);
|
|
21
17
|
useEffect(() => {
|
|
22
|
-
if (resolved.mode !== "terminal")
|
|
23
|
-
return;
|
|
24
|
-
setTermSize(resolved.size);
|
|
25
|
-
}, [resolved.mode, resolved.fit, resolved.size.cols, resolved.size.rows]);
|
|
26
|
-
useEffect(() => {
|
|
27
|
-
if (resolved.mode !== "terminal")
|
|
28
|
-
return;
|
|
29
18
|
if (resolved.fit !== "container")
|
|
30
19
|
return;
|
|
31
20
|
if (!wrapperRef.current)
|
|
32
21
|
return;
|
|
22
|
+
const wrapper = wrapperRef.current;
|
|
23
|
+
const estimatedCell = measureCellSize(resolved.terminal.fontSize, resolved.terminal.fontFamily);
|
|
24
|
+
const updateFromPixels = (width, height) => {
|
|
25
|
+
const cellW = cellSizeRef.current?.w ?? estimatedCell.w;
|
|
26
|
+
const cellH = cellSizeRef.current?.h ?? estimatedCell.h;
|
|
27
|
+
const newCols = Math.max(1, Math.floor(width / cellW));
|
|
28
|
+
const newRows = Math.max(1, Math.floor(height / cellH));
|
|
29
|
+
setTermSize((prev) => {
|
|
30
|
+
if (prev && prev.cols === newCols && prev.rows === newRows)
|
|
31
|
+
return prev;
|
|
32
|
+
return { cols: newCols, rows: newRows };
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
const rect = wrapper.getBoundingClientRect();
|
|
36
|
+
if (rect.width > 0 && rect.height > 0) {
|
|
37
|
+
updateFromPixels(rect.width, rect.height);
|
|
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.
|
|
33
42
|
const observer = new ResizeObserver(([entry]) => {
|
|
34
43
|
const { width, height } = entry.contentRect;
|
|
35
44
|
if (width > 0 && height > 0) {
|
|
36
|
-
|
|
37
|
-
const cellH = cellSizeRef.current?.h ?? resolved.terminal.fontSize * 1.2;
|
|
38
|
-
setTermSize({
|
|
39
|
-
cols: Math.max(1, Math.floor(width / cellW)),
|
|
40
|
-
rows: Math.max(1, Math.floor(height / cellH)),
|
|
41
|
-
});
|
|
45
|
+
updateFromPixels(width, height);
|
|
42
46
|
}
|
|
43
47
|
});
|
|
44
|
-
observer.observe(
|
|
48
|
+
observer.observe(wrapper);
|
|
45
49
|
return () => observer.disconnect();
|
|
46
|
-
}, [resolved.
|
|
50
|
+
}, [resolved.fit, resolved.terminal.fontSize, resolved.terminal.fontFamily]);
|
|
47
51
|
useEffect(() => {
|
|
48
|
-
if (resolved.mode !== "terminal")
|
|
49
|
-
return;
|
|
50
52
|
if (!termSize || !containerRef.current)
|
|
51
53
|
return;
|
|
52
54
|
let cancelled = false;
|
|
55
|
+
let disposeRenderSurface = null;
|
|
53
56
|
const container = containerRef.current;
|
|
54
57
|
const activeSize = termSize;
|
|
55
58
|
const setStatusAndNotify = (next) => {
|
|
@@ -65,44 +68,47 @@ export function TuiPreview(props) {
|
|
|
65
68
|
setErrorMsg("");
|
|
66
69
|
async function setup() {
|
|
67
70
|
try {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
let appCols = activeSize.cols;
|
|
72
|
+
let appRows = activeSize.rows;
|
|
73
|
+
let bridge = null;
|
|
74
|
+
const surface = await createMiniTerminalSurface({
|
|
75
|
+
container,
|
|
73
76
|
cols: activeSize.cols,
|
|
74
77
|
rows: activeSize.rows,
|
|
75
78
|
fontSize: resolved.terminal.fontSize,
|
|
76
79
|
fontFamily: resolved.terminal.fontFamily,
|
|
77
80
|
theme: resolved.terminal.theme,
|
|
78
|
-
disableStdin: !resolved.interactive,
|
|
79
|
-
cursorBlink: resolved.terminal.cursorBlink,
|
|
80
81
|
convertEol: resolved.terminal.convertEol,
|
|
82
|
+
interactive: resolved.mode !== "static" && resolved.interactive,
|
|
83
|
+
showCursor: resolved.mode !== "static",
|
|
84
|
+
wasmUrl: resolved.terminal.wasmUrl,
|
|
85
|
+
onInput: (data) => bridge?.pushInput(data),
|
|
81
86
|
});
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
let appRows = term.rows;
|
|
86
|
-
if (resolved.fit === "container") {
|
|
87
|
-
const fitAddon = new ghostty.FitAddon();
|
|
88
|
-
term.loadAddon(fitAddon);
|
|
89
|
-
fitAddon.fit();
|
|
90
|
-
appCols = term.cols;
|
|
91
|
-
appRows = term.rows;
|
|
92
|
-
if (wrapperRef.current && appCols > 0 && appRows > 0) {
|
|
93
|
-
cellSizeRef.current = {
|
|
94
|
-
w: wrapperRef.current.clientWidth / appCols,
|
|
95
|
-
h: wrapperRef.current.clientHeight / appRows,
|
|
96
|
-
};
|
|
97
|
-
}
|
|
87
|
+
if (cancelled) {
|
|
88
|
+
surface.dispose();
|
|
89
|
+
return;
|
|
98
90
|
}
|
|
91
|
+
disposeRenderSurface = () => surface.dispose();
|
|
92
|
+
appCols = surface.cols;
|
|
93
|
+
appRows = surface.rows;
|
|
94
|
+
cellSizeRef.current = surface.cellSize;
|
|
99
95
|
const resolvedArgs = resolved.resolveArgv({ cols: appCols, rows: appRows });
|
|
100
|
-
const
|
|
101
|
-
const
|
|
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({
|
|
102
108
|
args: [resolved.wasm.toString(), ...resolvedArgs],
|
|
103
109
|
env: resolved.env,
|
|
104
|
-
stdout: (data) =>
|
|
105
|
-
stderr: (data) =>
|
|
110
|
+
stdout: (data) => flushSurfaceOutput(data, stdoutDecoder),
|
|
111
|
+
stderr: (data) => flushSurfaceOutput(data, stderrDecoder),
|
|
106
112
|
onExit: (code) => {
|
|
107
113
|
if (!cancelled) {
|
|
108
114
|
setStatusAndNotify("exited");
|
|
@@ -110,9 +116,6 @@ export function TuiPreview(props) {
|
|
|
110
116
|
}
|
|
111
117
|
},
|
|
112
118
|
});
|
|
113
|
-
if (resolved.interactive) {
|
|
114
|
-
term.onData((data) => bridge.pushInput(data));
|
|
115
|
-
}
|
|
116
119
|
const wasmApp = await instantiateApp(resolved.wasm, bridge);
|
|
117
120
|
if (cancelled)
|
|
118
121
|
return;
|
|
@@ -136,8 +139,8 @@ export function TuiPreview(props) {
|
|
|
136
139
|
setup();
|
|
137
140
|
return () => {
|
|
138
141
|
cancelled = true;
|
|
139
|
-
|
|
140
|
-
|
|
142
|
+
disposeRenderSurface?.();
|
|
143
|
+
disposeRenderSurface = null;
|
|
141
144
|
};
|
|
142
145
|
}, [
|
|
143
146
|
resolved.mode,
|
|
@@ -153,97 +156,13 @@ export function TuiPreview(props) {
|
|
|
153
156
|
resolved.terminal.fontSize,
|
|
154
157
|
resolved.terminal.fontFamily,
|
|
155
158
|
resolved.terminal.theme,
|
|
156
|
-
resolved.terminal.
|
|
159
|
+
resolved.terminal.wasmUrl,
|
|
157
160
|
resolved.terminal.convertEol,
|
|
158
161
|
]);
|
|
159
|
-
// ── Static mode effect ───────────────────────────────────────────────────
|
|
160
|
-
useEffect(() => {
|
|
161
|
-
if (resolved.mode !== "static")
|
|
162
|
-
return;
|
|
163
|
-
let cancelled = false;
|
|
164
|
-
const setStatusAndNotify = (next) => {
|
|
165
|
-
setStatus(next);
|
|
166
|
-
resolved.onStatusChange?.(next);
|
|
167
|
-
};
|
|
168
|
-
setStatusAndNotify("loading");
|
|
169
|
-
setErrorMsg("");
|
|
170
|
-
setStaticHtml(null);
|
|
171
|
-
async function run() {
|
|
172
|
-
try {
|
|
173
|
-
const decoder = new TextDecoder();
|
|
174
|
-
const chunks = [];
|
|
175
|
-
const bridge = new WasiBridge({
|
|
176
|
-
args: [resolved.wasm.toString(), ...resolved.resolveArgv(resolved.size)],
|
|
177
|
-
env: resolved.env,
|
|
178
|
-
stdout: (data) => chunks.push(new Uint8Array(data)),
|
|
179
|
-
stderr: () => { },
|
|
180
|
-
onExit: (code) => {
|
|
181
|
-
if (!cancelled) {
|
|
182
|
-
const total = chunks.reduce((n, c) => n + c.length, 0);
|
|
183
|
-
const merged = new Uint8Array(total);
|
|
184
|
-
let offset = 0;
|
|
185
|
-
for (const c of chunks) {
|
|
186
|
-
merged.set(c, offset);
|
|
187
|
-
offset += c.length;
|
|
188
|
-
}
|
|
189
|
-
setStaticHtml(ansiToHtml(decoder.decode(merged)));
|
|
190
|
-
setStatusAndNotify("exited");
|
|
191
|
-
resolved.onExit?.(code);
|
|
192
|
-
}
|
|
193
|
-
},
|
|
194
|
-
});
|
|
195
|
-
const wasmApp = await instantiateApp(resolved.wasm, bridge);
|
|
196
|
-
if (cancelled)
|
|
197
|
-
return;
|
|
198
|
-
setStatusAndNotify("running");
|
|
199
|
-
await wasmApp.run();
|
|
200
|
-
}
|
|
201
|
-
catch (e) {
|
|
202
|
-
if (!cancelled) {
|
|
203
|
-
setStatusAndNotify("error");
|
|
204
|
-
setErrorMsg(e instanceof Error ? e.message : String(e));
|
|
205
|
-
resolved.onError?.(e);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
run();
|
|
210
|
-
return () => { cancelled = true; };
|
|
211
|
-
}, [
|
|
212
|
-
resolved.mode,
|
|
213
|
-
resolved.wasm,
|
|
214
|
-
resolved.resolveArgv,
|
|
215
|
-
resolved.env,
|
|
216
|
-
resolved.size.cols,
|
|
217
|
-
resolved.size.rows,
|
|
218
|
-
resolved.onExit,
|
|
219
|
-
resolved.onError,
|
|
220
|
-
resolved.onStatusChange,
|
|
221
|
-
]);
|
|
222
|
-
// ── Render ───────────────────────────────────────────────────────────────
|
|
223
|
-
const bg = resolved.terminal.theme?.background ?? "#1a1b26";
|
|
224
|
-
const fg = resolved.terminal.theme?.foreground ?? "#a9b1d6";
|
|
225
|
-
if (resolved.mode === "static") {
|
|
226
|
-
return (_jsxs("div", { className: props.className, style: {
|
|
227
|
-
position: "relative",
|
|
228
|
-
background: bg,
|
|
229
|
-
borderRadius: 6,
|
|
230
|
-
overflow: "hidden",
|
|
231
|
-
...props.style,
|
|
232
|
-
}, children: [status === "loading" && _jsx("div", { style: overlayStyle, children: "Loading\u2026" }), status === "error" && (_jsxs("div", { style: { ...overlayStyle, color: "#f7768e" }, children: ["Error: ", errorMsg] })), staticHtml !== null && (_jsx("pre", { style: {
|
|
233
|
-
margin: 0,
|
|
234
|
-
padding: "0.5em",
|
|
235
|
-
fontFamily: resolved.terminal.fontFamily,
|
|
236
|
-
fontSize: resolved.terminal.fontSize,
|
|
237
|
-
color: fg,
|
|
238
|
-
lineHeight: 1.2,
|
|
239
|
-
background: "transparent",
|
|
240
|
-
overflow: "auto",
|
|
241
|
-
}, dangerouslySetInnerHTML: { __html: staticHtml } }))] }));
|
|
242
|
-
}
|
|
243
162
|
return (_jsxs("div", { ref: wrapperRef, className: props.className, style: {
|
|
244
163
|
position: "relative",
|
|
245
|
-
display: "inline-block",
|
|
246
|
-
background:
|
|
164
|
+
display: resolved.fit === "container" ? "block" : "inline-block",
|
|
165
|
+
background: resolved.terminal.theme?.background ?? "#1a1b26",
|
|
247
166
|
borderRadius: 6,
|
|
248
167
|
overflow: "hidden",
|
|
249
168
|
...props.style,
|
package/dist/core/index.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export {
|
|
3
|
-
export { resolveTuiPreviewProps, warnLegacyPropsOnce } from "./normalize.js";
|
|
1
|
+
export { createMiniTerminalSurface, loadLibGhostty, measureCellSize } from "./libghostty.js";
|
|
2
|
+
export { resolveTuiPreviewProps } from "./normalize.js";
|
|
4
3
|
export { WasiBridge, WasiExitError, instantiateApp } from "./wasi.js";
|
package/dist/core/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export {
|
|
3
|
-
export { resolveTuiPreviewProps, warnLegacyPropsOnce } from "./normalize.js";
|
|
1
|
+
export { createMiniTerminalSurface, loadLibGhostty, measureCellSize } from "./libghostty.js";
|
|
2
|
+
export { resolveTuiPreviewProps } from "./normalize.js";
|
|
4
3
|
export { WasiBridge, WasiExitError, instantiateApp } from "./wasi.js";
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { GhosttyTheme } from "../types.js";
|
|
2
|
+
interface LibGhosttyExports extends WebAssembly.Exports {
|
|
3
|
+
memory: WebAssembly.Memory;
|
|
4
|
+
ghostty_wasm_alloc_u8_array(len: number): number;
|
|
5
|
+
ghostty_wasm_free_u8_array(ptr: number, len: number): void;
|
|
6
|
+
ghostty_terminal_new(cols: number, rows: number): number;
|
|
7
|
+
ghostty_terminal_new_with_config(cols: number, rows: number, configPtr: number): number;
|
|
8
|
+
ghostty_terminal_free(handle: number): void;
|
|
9
|
+
ghostty_terminal_resize(handle: number, cols: number, rows: number): void;
|
|
10
|
+
ghostty_terminal_write(handle: number, dataPtr: number, dataLen: number): void;
|
|
11
|
+
ghostty_render_state_update(handle: number): number;
|
|
12
|
+
ghostty_render_state_get_cols(handle: number): number;
|
|
13
|
+
ghostty_render_state_get_rows(handle: number): number;
|
|
14
|
+
ghostty_render_state_is_row_dirty(handle: number, row: number): boolean;
|
|
15
|
+
ghostty_render_state_mark_clean(handle: number): void;
|
|
16
|
+
ghostty_render_state_get_viewport(handle: number, bufPtr: number, bufLen: number): number;
|
|
17
|
+
ghostty_terminal_has_response(handle: number): boolean;
|
|
18
|
+
ghostty_terminal_read_response(handle: number, bufPtr: number, bufLen: number): number;
|
|
19
|
+
}
|
|
20
|
+
export interface MiniTerminalSurfaceOptions {
|
|
21
|
+
container: HTMLElement;
|
|
22
|
+
/** Explicit cell dimensions — used directly when provided. */
|
|
23
|
+
cols?: number;
|
|
24
|
+
rows?: number;
|
|
25
|
+
/** Pixel dimensions — if provided, cols/rows are computed from font metrics. */
|
|
26
|
+
widthPx?: number;
|
|
27
|
+
heightPx?: number;
|
|
28
|
+
fontSize: number;
|
|
29
|
+
fontFamily: string;
|
|
30
|
+
theme?: Partial<GhosttyTheme>;
|
|
31
|
+
convertEol: boolean;
|
|
32
|
+
interactive: boolean;
|
|
33
|
+
showCursor: boolean;
|
|
34
|
+
onInput?: (data: string) => void;
|
|
35
|
+
wasmUrl?: string | URL;
|
|
36
|
+
}
|
|
37
|
+
export interface MiniTerminalSurface {
|
|
38
|
+
cols: number;
|
|
39
|
+
rows: number;
|
|
40
|
+
cellSize: {
|
|
41
|
+
w: number;
|
|
42
|
+
h: number;
|
|
43
|
+
};
|
|
44
|
+
write(text: string): void;
|
|
45
|
+
drainResponses(): string[];
|
|
46
|
+
dispose(): void;
|
|
47
|
+
}
|
|
48
|
+
declare class LibGhosttyRuntime {
|
|
49
|
+
private readonly wasm;
|
|
50
|
+
private readonly abi;
|
|
51
|
+
constructor(wasm: LibGhosttyExports, abi: {
|
|
52
|
+
cellSize: number;
|
|
53
|
+
terminalConfigSize: number;
|
|
54
|
+
});
|
|
55
|
+
createTerminal(cols: number, rows: number, theme: GhosttyTheme): LibGhosttyTerminal;
|
|
56
|
+
}
|
|
57
|
+
declare class LibGhosttyTerminal {
|
|
58
|
+
private readonly wasm;
|
|
59
|
+
private readonly handle;
|
|
60
|
+
cols: number;
|
|
61
|
+
rows: number;
|
|
62
|
+
private readonly cellSize;
|
|
63
|
+
private viewportPtr;
|
|
64
|
+
private viewportLen;
|
|
65
|
+
constructor(wasm: LibGhosttyExports, handle: number, cols: number, rows: number, cellSize: number);
|
|
66
|
+
write(textOrData: string | Uint8Array): void;
|
|
67
|
+
resize(cols: number, rows: number): void;
|
|
68
|
+
hasResponse(): boolean;
|
|
69
|
+
isDirty(): boolean;
|
|
70
|
+
readResponse(maxBytes?: number): string | null;
|
|
71
|
+
getViewportData(): {
|
|
72
|
+
cols: number;
|
|
73
|
+
rows: number;
|
|
74
|
+
buffer: Uint8Array;
|
|
75
|
+
};
|
|
76
|
+
dispose(): void;
|
|
77
|
+
private releaseViewport;
|
|
78
|
+
}
|
|
79
|
+
/** Measure monospace cell size for a given font. Cheap and synchronous. */
|
|
80
|
+
export declare function measureCellSize(fontSize: number, fontFamily: string): {
|
|
81
|
+
w: number;
|
|
82
|
+
h: number;
|
|
83
|
+
};
|
|
84
|
+
export declare function loadLibGhostty(wasmUrl?: string | URL): Promise<LibGhosttyRuntime>;
|
|
85
|
+
export declare function createMiniTerminalSurface(options: MiniTerminalSurfaceOptions): Promise<MiniTerminalSurface>;
|
|
86
|
+
export {};
|