@dkkoval/tui-preview 0.1.1 → 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 +62 -54
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.js +2 -2
- 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 -67
- package/dist/core/wasi.d.ts +1 -1
- package/dist/core/wasi.js +16 -3
- package/dist/ghostty-vt.wasm +0 -0
- package/dist/index.cjs +2 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +613 -227
- package/dist/types.d.ts +8 -26
- package/package.json +6 -7
- package/dist/__vite-browser-external-2447137e-BcPniuRQ.cjs +0 -1
- package/dist/__vite-browser-external-2447137e-DYxpcVy9.js +0 -4
- 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/dist/core/normalize.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
const DEFAULT_SIZE = { cols: 80, rows: 24 };
|
|
2
2
|
const EMPTY_ENV = {};
|
|
3
3
|
const EMPTY_ARGV = [];
|
|
4
|
-
function
|
|
5
|
-
|
|
4
|
+
function normalizeMode(mode) {
|
|
5
|
+
if (mode === "static") {
|
|
6
|
+
return "static";
|
|
7
|
+
}
|
|
8
|
+
return "interactive";
|
|
6
9
|
}
|
|
7
10
|
function resolveArgvInput(argv) {
|
|
8
11
|
const value = argv ?? EMPTY_ARGV;
|
|
@@ -11,80 +14,30 @@ function resolveArgvInput(argv) {
|
|
|
11
14
|
}
|
|
12
15
|
return () => value;
|
|
13
16
|
}
|
|
14
|
-
function resolveLegacySize(props) {
|
|
15
|
-
const hasExplicitSize = props.cols !== undefined || props.rows !== undefined;
|
|
16
|
-
if (!hasExplicitSize) {
|
|
17
|
-
return { fit: "container", size: DEFAULT_SIZE };
|
|
18
|
-
}
|
|
19
|
-
return {
|
|
20
|
-
fit: "none",
|
|
21
|
-
size: {
|
|
22
|
-
cols: Math.max(1, props.cols ?? DEFAULT_SIZE.cols),
|
|
23
|
-
rows: Math.max(1, props.rows ?? DEFAULT_SIZE.rows),
|
|
24
|
-
},
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
let warnedLegacyProps = false;
|
|
28
|
-
export function warnLegacyPropsOnce(usedLegacyProps) {
|
|
29
|
-
if (!usedLegacyProps || warnedLegacyProps)
|
|
30
|
-
return;
|
|
31
|
-
warnedLegacyProps = true;
|
|
32
|
-
console.warn("[tui-preview] Legacy props (`app`, `args`, `cols`, `rows`, `fontSize`, `fontFamily`, `theme`) are deprecated. " +
|
|
33
|
-
"Use `wasm`, `argv`, `fit`, `size`, and `terminal`.");
|
|
34
|
-
}
|
|
35
17
|
export function resolveTuiPreviewProps(props) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
: {
|
|
44
|
-
cols: Math.max(1, props.size?.cols ?? DEFAULT_SIZE.cols),
|
|
45
|
-
rows: Math.max(1, props.size?.rows ?? DEFAULT_SIZE.rows),
|
|
46
|
-
};
|
|
47
|
-
const mode = (props.mode ?? "terminal");
|
|
48
|
-
return {
|
|
49
|
-
wasm: props.wasm,
|
|
50
|
-
env: props.env ?? EMPTY_ENV,
|
|
51
|
-
interactive: mode === "static" ? false : (props.interactive ?? true),
|
|
52
|
-
mode,
|
|
53
|
-
fit,
|
|
54
|
-
size,
|
|
55
|
-
terminal: {
|
|
56
|
-
fontSize: props.terminal?.fontSize ?? 14,
|
|
57
|
-
fontFamily: props.terminal?.fontFamily ?? "monospace",
|
|
58
|
-
cursorBlink: props.terminal?.cursorBlink ?? true,
|
|
59
|
-
convertEol: props.terminal?.convertEol ?? true,
|
|
60
|
-
theme: props.terminal?.theme,
|
|
61
|
-
},
|
|
62
|
-
resolveArgv: resolveArgvInput(props.argv),
|
|
63
|
-
onExit: props.onExit,
|
|
64
|
-
onError: props.onError,
|
|
65
|
-
onStatusChange: props.onStatusChange,
|
|
66
|
-
usedLegacyProps: false,
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
const { fit, size } = resolveLegacySize(props);
|
|
18
|
+
const fit = props.fit ?? (props.size ? "none" : "container");
|
|
19
|
+
const size = {
|
|
20
|
+
cols: Math.max(1, props.size?.cols ?? DEFAULT_SIZE.cols),
|
|
21
|
+
rows: Math.max(1, props.size?.rows ?? DEFAULT_SIZE.rows),
|
|
22
|
+
};
|
|
23
|
+
const mode = normalizeMode(props.mode);
|
|
70
24
|
return {
|
|
71
|
-
wasm: props.
|
|
25
|
+
wasm: props.wasm,
|
|
72
26
|
env: props.env ?? EMPTY_ENV,
|
|
73
|
-
interactive: props.interactive ?? true,
|
|
74
|
-
mode
|
|
27
|
+
interactive: mode === "static" ? false : (props.interactive ?? true),
|
|
28
|
+
mode,
|
|
75
29
|
fit,
|
|
76
30
|
size,
|
|
77
31
|
terminal: {
|
|
78
|
-
fontSize: props.fontSize ?? 14,
|
|
79
|
-
fontFamily: props.fontFamily ?? "monospace",
|
|
80
|
-
|
|
81
|
-
convertEol: true,
|
|
82
|
-
theme: props.theme,
|
|
32
|
+
fontSize: props.terminal?.fontSize ?? 14,
|
|
33
|
+
fontFamily: props.terminal?.fontFamily ?? "monospace",
|
|
34
|
+
wasmUrl: props.terminal?.wasmUrl,
|
|
35
|
+
convertEol: props.terminal?.convertEol ?? true,
|
|
36
|
+
theme: props.terminal?.theme,
|
|
83
37
|
},
|
|
84
|
-
resolveArgv: resolveArgvInput(props.
|
|
38
|
+
resolveArgv: resolveArgvInput(props.argv),
|
|
85
39
|
onExit: props.onExit,
|
|
86
40
|
onError: props.onError,
|
|
87
41
|
onStatusChange: props.onStatusChange,
|
|
88
|
-
usedLegacyProps: true,
|
|
89
42
|
};
|
|
90
43
|
}
|
package/dist/core/wasi.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* WASI bridge — connects a wasm32-wasi TUI app to a
|
|
2
|
+
* WASI bridge — connects a wasm32-wasi TUI app to a terminal surface.
|
|
3
3
|
*
|
|
4
4
|
* Implements a minimal WASI preview1 surface sufficient for interactive TUI apps:
|
|
5
5
|
* - fd_write (stdout/stderr → terminal)
|
package/dist/core/wasi.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* WASI bridge — connects a wasm32-wasi TUI app to a
|
|
2
|
+
* WASI bridge — connects a wasm32-wasi TUI app to a terminal surface.
|
|
3
3
|
*
|
|
4
4
|
* Implements a minimal WASI preview1 surface sufficient for interactive TUI apps:
|
|
5
5
|
* - fd_write (stdout/stderr → terminal)
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
const WASI_ESUCCESS = 0;
|
|
13
13
|
const WASI_EAGAIN = 6;
|
|
14
14
|
const WASI_BADF = 8;
|
|
15
|
+
const WASI_EVENTTYPE_FD_READ = 1;
|
|
15
16
|
const STDIN_FD = 0;
|
|
16
17
|
const STDOUT_FD = 1;
|
|
17
18
|
const STDERR_FD = 2;
|
|
@@ -115,7 +116,7 @@ export class WasiBridge {
|
|
|
115
116
|
fd_read: (fd, iovsPtr, iovsLen, nreadPtr) => {
|
|
116
117
|
if (fd !== STDIN_FD)
|
|
117
118
|
return WASI_BADF;
|
|
118
|
-
const chunk = this.inputQueue
|
|
119
|
+
const chunk = this.inputQueue[0];
|
|
119
120
|
if (!chunk)
|
|
120
121
|
return WASI_EAGAIN;
|
|
121
122
|
const view = this.view();
|
|
@@ -128,6 +129,12 @@ export class WasiBridge {
|
|
|
128
129
|
u8.set(chunk.subarray(nread, nread + toCopy), ptr);
|
|
129
130
|
nread += toCopy;
|
|
130
131
|
}
|
|
132
|
+
if (nread >= chunk.length) {
|
|
133
|
+
this.inputQueue.shift();
|
|
134
|
+
}
|
|
135
|
+
else if (nread > 0) {
|
|
136
|
+
this.inputQueue[0] = chunk.subarray(nread);
|
|
137
|
+
}
|
|
131
138
|
view.setUint32(nreadPtr, nread, true);
|
|
132
139
|
return WASI_ESUCCESS;
|
|
133
140
|
},
|
|
@@ -137,7 +144,7 @@ export class WasiBridge {
|
|
|
137
144
|
for (let i = 0; i < nsubscriptions; i++) {
|
|
138
145
|
const subPtr = inPtr + i * 48;
|
|
139
146
|
const type = view.getUint8(subPtr + 8);
|
|
140
|
-
if (type ===
|
|
147
|
+
if (type === WASI_EVENTTYPE_FD_READ && this.inputQueue.length > 0) {
|
|
141
148
|
const evPtr = outPtr + nevents * 32;
|
|
142
149
|
view.setBigUint64(evPtr, view.getBigUint64(subPtr, true), true);
|
|
143
150
|
view.setUint16(evPtr + 8, 0, true);
|
|
@@ -199,7 +206,13 @@ export async function instantiateApp(source, bridge) {
|
|
|
199
206
|
let module = moduleCache.get(key);
|
|
200
207
|
if (!module) {
|
|
201
208
|
const response = await fetch(source);
|
|
209
|
+
if (!response.ok) {
|
|
210
|
+
throw new Error(`Failed to load app wasm: ${response.status} ${response.statusText}`);
|
|
211
|
+
}
|
|
202
212
|
const bytes = await response.arrayBuffer();
|
|
213
|
+
if (bytes.byteLength === 0) {
|
|
214
|
+
throw new Error("App wasm is empty.");
|
|
215
|
+
}
|
|
203
216
|
module = await WebAssembly.compile(bytes);
|
|
204
217
|
moduleCache.set(key, module);
|
|
205
218
|
}
|
|
Binary file
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
"use strict";var
|
|
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;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { TuiPreview } from "./TuiPreview.js";
|
|
2
|
-
export type { GhosttyTheme, ResolvedTuiPreviewOptions, TuiArgv, TuiFitMode, TuiRenderMode, TuiPreviewCommonProps,
|
|
3
|
-
export { WasiBridge, WasiExitError, instantiateApp } from "./wasi.js";
|
|
2
|
+
export type { GhosttyTheme, ResolvedTuiPreviewOptions, TuiArgv, TuiFitMode, TuiRenderMode, TuiPreviewCommonProps, TuiPreviewModernProps, TuiPreviewProps, TuiPreviewStatus, TuiRuntimeSize, TuiTerminalOptions, WasiOptions, } from "./types.js";
|
|
3
|
+
export { WasiBridge, WasiExitError, instantiateApp } from "./core/wasi.js";
|