@dkkoval/tui-preview 0.1.0 → 0.1.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.
@@ -1,6 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useMemo, useRef, useState } from "react";
3
- import { ansiToHtml } from "./core/ansi.js";
4
3
  import { loadGhostty } from "./core/ghostty.js";
5
4
  import { resolveTuiPreviewProps, warnLegacyPropsOnce } from "./core/normalize.js";
6
5
  import { WasiBridge, instantiateApp } from "./core/wasi.js";
@@ -13,19 +12,13 @@ export function TuiPreview(props) {
13
12
  const [errorMsg, setErrorMsg] = useState("");
14
13
  const cellSizeRef = useRef(null);
15
14
  const [termSize, setTermSize] = useState(resolved.size);
16
- const [staticHtml, setStaticHtml] = useState(null);
17
15
  useEffect(() => {
18
16
  warnLegacyPropsOnce(resolved.usedLegacyProps);
19
17
  }, [resolved.usedLegacyProps]);
20
- // ── Terminal mode effects ────────────────────────────────────────────────
21
18
  useEffect(() => {
22
- if (resolved.mode !== "terminal")
23
- return;
24
19
  setTermSize(resolved.size);
25
- }, [resolved.mode, resolved.fit, resolved.size.cols, resolved.size.rows]);
20
+ }, [resolved.fit, resolved.size.cols, resolved.size.rows]);
26
21
  useEffect(() => {
27
- if (resolved.mode !== "terminal")
28
- return;
29
22
  if (resolved.fit !== "container")
30
23
  return;
31
24
  if (!wrapperRef.current)
@@ -43,10 +36,8 @@ export function TuiPreview(props) {
43
36
  });
44
37
  observer.observe(wrapperRef.current);
45
38
  return () => observer.disconnect();
46
- }, [resolved.mode, resolved.fit, resolved.terminal.fontSize]);
39
+ }, [resolved.fit, resolved.terminal.fontSize]);
47
40
  useEffect(() => {
48
- if (resolved.mode !== "terminal")
49
- return;
50
41
  if (!termSize || !containerRef.current)
51
42
  return;
52
43
  let cancelled = false;
@@ -81,6 +72,10 @@ export function TuiPreview(props) {
81
72
  });
82
73
  termRef.current = term;
83
74
  term.open(container);
75
+ // Static mode: hide cursor via DEC PM — app output only, no cursor chrome.
76
+ if (resolved.mode === "static") {
77
+ term.write("\x1b[?25l");
78
+ }
84
79
  let appCols = term.cols;
85
80
  let appRows = term.rows;
86
81
  if (resolved.fit === "container") {
@@ -156,94 +151,10 @@ export function TuiPreview(props) {
156
151
  resolved.terminal.cursorBlink,
157
152
  resolved.terminal.convertEol,
158
153
  ]);
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
154
  return (_jsxs("div", { ref: wrapperRef, className: props.className, style: {
244
155
  position: "relative",
245
156
  display: "inline-block",
246
- background: bg,
157
+ background: resolved.terminal.theme?.background ?? "#1a1b26",
247
158
  borderRadius: 6,
248
159
  overflow: "hidden",
249
160
  ...props.style,
@@ -1,4 +1,3 @@
1
- export { ansiToHtml } from "./ansi.js";
2
1
  export { loadGhostty } from "./ghostty.js";
3
2
  export { resolveTuiPreviewProps, warnLegacyPropsOnce } from "./normalize.js";
4
3
  export { WasiBridge, WasiExitError, instantiateApp } from "./wasi.js";
@@ -1,4 +1,3 @@
1
- export { ansiToHtml } from "./ansi.js";
2
1
  export { loadGhostty } from "./ghostty.js";
3
2
  export { resolveTuiPreviewProps, warnLegacyPropsOnce } from "./normalize.js";
4
3
  export { WasiBridge, WasiExitError, instantiateApp } from "./wasi.js";
@@ -44,11 +44,12 @@ export function resolveTuiPreviewProps(props) {
44
44
  cols: Math.max(1, props.size?.cols ?? DEFAULT_SIZE.cols),
45
45
  rows: Math.max(1, props.size?.rows ?? DEFAULT_SIZE.rows),
46
46
  };
47
+ const mode = (props.mode ?? "terminal");
47
48
  return {
48
49
  wasm: props.wasm,
49
50
  env: props.env ?? EMPTY_ENV,
50
- interactive: props.interactive ?? true,
51
- mode: (props.mode ?? "terminal"),
51
+ interactive: mode === "static" ? false : (props.interactive ?? true),
52
+ mode,
52
53
  fit,
53
54
  size,
54
55
  terminal: {
package/dist/core/wasi.js CHANGED
@@ -191,11 +191,18 @@ export class WasiExitError extends Error {
191
191
  this.code = code;
192
192
  }
193
193
  }
194
+ /** Compiled module cache — keyed by URL string, persists for the page lifetime. */
195
+ const moduleCache = new Map();
194
196
  /** Load and instantiate a WASM TUI app with a WasiBridge */
195
197
  export async function instantiateApp(source, bridge) {
196
- const response = await fetch(source);
197
- const bytes = await response.arrayBuffer();
198
- const module = await WebAssembly.compile(bytes);
198
+ const key = source.toString();
199
+ let module = moduleCache.get(key);
200
+ if (!module) {
201
+ const response = await fetch(source);
202
+ const bytes = await response.arrayBuffer();
203
+ module = await WebAssembly.compile(bytes);
204
+ moduleCache.set(key, module);
205
+ }
199
206
  const importObject = {
200
207
  wasi_snapshot_preview1: bridge.imports,
201
208
  };
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";var K=Object.defineProperty;var X=(t,e,n)=>e in t?K(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n;var W=(t,e,n)=>X(t,typeof e!="symbol"?e+"":e,n);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const M=require("react/jsx-runtime"),b=require("react"),j=["#000000","#cd0000","#00cd00","#cdcd00","#0000ee","#cd00cd","#00cdcd","#e5e5e5","#7f7f7f","#ff0000","#00ff00","#ffff00","#5c5cff","#ff00ff","#00ffff","#ffffff"];function Q(t){if(t<16)return j[t];if(t<232){const n=t-16,o=Math.floor(n/36),a=Math.floor(n%36/6),r=n%6,i=c=>c===0?0:c*40+55;return`rgb(${i(o)},${i(a)},${i(r)})`}const e=(t-232)*10+8;return`rgb(${e},${e},${e})`}function V(t){return t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function ee(t){let e=null,n=null,o=!1,a=!1,r=!1;const i=[];let c="",u="";function h(){const s=[];return e&&s.push(`color:${e}`),n&&s.push(`background-color:${n}`),o&&s.push("font-weight:bold"),a&&s.push("font-style:italic"),r&&s.push("text-decoration:underline"),s.join(";")}function m(){c&&(i.push(u?`<span style="${u}">${V(c)}</span>`:V(c)),c="")}function v(s){m();let f=0;for(;f<s.length;){const l=s[f];l===0?(e=null,n=null,o=!1,a=!1,r=!1):l===1?o=!0:l===22?o=!1:l===3?a=!0:l===23?a=!1:l===4?r=!0:l===24?r=!1:l>=30&&l<=37?e=j[l-30]:l===38?s[f+1]===2&&f+4<s.length?(e=`rgb(${s[f+2]},${s[f+3]},${s[f+4]})`,f+=4):s[f+1]===5&&f+2<s.length&&(e=Q(s[f+2]),f+=2):l===39?e=null:l>=40&&l<=47?n=j[l-40]:l===48?s[f+1]===2&&f+4<s.length?(n=`rgb(${s[f+2]},${s[f+3]},${s[f+4]})`,f+=4):s[f+1]===5&&f+2<s.length&&(n=Q(s[f+2]),f+=2):l===49?n=null:l>=90&&l<=97?e=j[l-82]:l>=100&&l<=107&&(n=j[l-92]),f++}u=h()}let d=0;for(;d<t.length;){const s=t[d];if(s==="\x1B"&&d+1<t.length&&t[d+1]==="["){const f=d+2;let l=f;for(;l<t.length&&(t.charCodeAt(l)<64||t.charCodeAt(l)>126);)l++;if(l<t.length){if(t[l]==="m"){const F=t.slice(f,l),I=F?F.split(";").map(y=>parseInt(y,10)||0):[0];v(I)}d=l+1}else d++}else if(s==="\r")d++;else{const f=h();f!==u&&(m(),u=f),c+=s,d++}}return m(),i.join("")}let p=null;function te(){return p||(p=Promise.resolve().then(()=>require("./ghostty-web-DkOZu5AZ.cjs")).then(async t=>(await t.init(),t))),p}const k={cols:80,rows:24},G={},ne=[];function re(t){return"wasm"in t}function Y(t){const e=t??ne;return typeof e=="function"?e:()=>e}function ie(t){return t.cols!==void 0||t.rows!==void 0?{fit:"none",size:{cols:Math.max(1,t.cols??k.cols),rows:Math.max(1,t.rows??k.rows)}}:{fit:"container",size:k}}let Z=!1;function se(t){!t||Z||(Z=!0,console.warn("[tui-preview] Legacy props (`app`, `args`, `cols`, `rows`, `fontSize`, `fontFamily`, `theme`) are deprecated. Use `wasm`, `argv`, `fit`, `size`, and `terminal`."))}function oe(t){var o,a,r,i,c,u,h,m,v;if(re(t)){const d=t.fit??(t.size?"none":"container"),s=d==="none"?{cols:Math.max(1,((o=t.size)==null?void 0:o.cols)??k.cols),rows:Math.max(1,((a=t.size)==null?void 0:a.rows)??k.rows)}:{cols:Math.max(1,((r=t.size)==null?void 0:r.cols)??k.cols),rows:Math.max(1,((i=t.size)==null?void 0:i.rows)??k.rows)};return{wasm:t.wasm,env:t.env??G,interactive:t.interactive??!0,mode:t.mode??"terminal",fit:d,size:s,terminal:{fontSize:((c=t.terminal)==null?void 0:c.fontSize)??14,fontFamily:((u=t.terminal)==null?void 0:u.fontFamily)??"monospace",cursorBlink:((h=t.terminal)==null?void 0:h.cursorBlink)??!0,convertEol:((m=t.terminal)==null?void 0:m.convertEol)??!0,theme:(v=t.terminal)==null?void 0:v.theme},resolveArgv:Y(t.argv),onExit:t.onExit,onError:t.onError,onStatusChange:t.onStatusChange,usedLegacyProps:!1}}const{fit:e,size:n}=ie(t);return{wasm:t.app,env:t.env??G,interactive:t.interactive??!0,mode:"terminal",fit:e,size:n,terminal:{fontSize:t.fontSize??14,fontFamily:t.fontFamily??"monospace",cursorBlink:!0,convertEol:!0,theme:t.theme},resolveArgv:Y(t.args),onExit:t.onExit,onError:t.onError,onStatusChange:t.onStatusChange,usedLegacyProps:!0}}const z=0,ce=6,P=8,le=0,J=1,ae=2;class N{constructor(e){W(this,"inputQueue",[]);W(this,"memory");this.opts=e}pushInput(e){const n=typeof e=="string"?new TextEncoder().encode(e):e;this.inputQueue.push(n)}attachMemory(e){this.memory=e}view(){return new DataView(this.memory.buffer)}u8(){return new Uint8Array(this.memory.buffer)}get imports(){return{args_sizes_get:(e,n)=>{const{args:o}=this.opts,a=new TextEncoder,r=o.reduce((i,c)=>i+a.encode(c).length+1,0);return this.view().setUint32(e,o.length,!0),this.view().setUint32(n,r,!0),z},args_get:(e,n)=>{const o=new TextEncoder,a=this.u8(),r=this.view();let i=n;return this.opts.args.forEach((c,u)=>{const h=o.encode(c);a.set(h,i),a[i+h.length]=0,r.setUint32(e+u*4,i,!0),i+=h.length+1}),z},environ_sizes_get:(e,n)=>{const o=this.envEntries(),a=new TextEncoder,r=o.reduce((i,c)=>i+a.encode(c).length+1,0);return this.view().setUint32(e,o.length,!0),this.view().setUint32(n,r,!0),z},environ_get:(e,n)=>{const o=new TextEncoder,a=this.u8(),r=this.view();let i=n;return this.envEntries().forEach((c,u)=>{const h=o.encode(c);a.set(h,i),a[i+h.length]=0,r.setUint32(e+u*4,i,!0),i+=h.length+1}),z},fd_write:(e,n,o,a)=>{if(e!==J&&e!==ae)return P;const r=this.view(),i=this.u8();let c=0;const u=[];for(let v=0;v<o;v++){const d=r.getUint32(n+v*8,!0),s=r.getUint32(n+v*8+4,!0);u.push(i.slice(d,d+s)),c+=s}const h=new Uint8Array(c);let m=0;for(const v of u)h.set(v,m),m+=v.length;return e===J?this.opts.stdout(h):this.opts.stderr(h),r.setUint32(a,c,!0),z},fd_read:(e,n,o,a)=>{if(e!==le)return P;const r=this.inputQueue.shift();if(!r)return ce;const i=this.view(),c=this.u8();let u=0;for(let h=0;h<o&&u<r.length;h++){const m=i.getUint32(n+h*8,!0),v=i.getUint32(n+h*8+4,!0),d=Math.min(v,r.length-u);c.set(r.subarray(u,u+d),m),u+=d}return i.setUint32(a,u,!0),z},poll_oneoff:(e,n,o,a)=>{const r=this.view();let i=0;for(let c=0;c<o;c++){const u=e+c*48,h=r.getUint8(u+8);if(h===0&&this.inputQueue.length>0){const m=n+i*32;r.setBigUint64(m,r.getBigUint64(u,!0),!0),r.setUint16(m+8,0,!0),r.setUint8(m+10,h),i++}}return r.setUint32(a,i,!0),z},proc_exit:e=>{throw this.opts.onExit(e),new H(e)},random_get:(e,n)=>(crypto.getRandomValues(new Uint8Array(this.memory.buffer,e,n)),z),fd_close:()=>z,fd_seek:()=>z,fd_fdstat_get:(e,n)=>(this.view().setUint8(n,e<=2?2:0),z),fd_prestat_get:()=>P,fd_prestat_dir_name:()=>P,path_open:()=>P,sched_yield:()=>z,clock_time_get:(e,n,o)=>{const a=BigInt(Date.now())*1000000n;return this.view().setBigUint64(o,a,!0),z}}}envEntries(){const e={TERM:"xterm-256color",COLORTERM:"truecolor",...this.opts.env};return Object.entries(e).map(([n,o])=>`${n}=${o}`)}}class H extends Error{constructor(e){super(`WASI exit: ${e}`),this.code=e}}async function O(t,e){const o=await(await fetch(t)).arrayBuffer(),a=await WebAssembly.compile(o),r={wasi_snapshot_preview1:e.imports},i=await WebAssembly.instantiate(a,r);e.attachMemory(i.exports.memory);const c=i.exports._start;if(!c)throw new Error("WASM module has no _start export");return{run:async()=>{try{c()}catch(u){if(!(u instanceof H))throw u}}}}function ue(t){var F,I;const e=b.useMemo(()=>oe(t),[t]),n=b.useRef(null),o=b.useRef(null),a=b.useRef(null),[r,i]=b.useState("loading"),[c,u]=b.useState(""),h=b.useRef(null),[m,v]=b.useState(e.size),[d,s]=b.useState(null);b.useEffect(()=>{se(e.usedLegacyProps)},[e.usedLegacyProps]),b.useEffect(()=>{e.mode==="terminal"&&v(e.size)},[e.mode,e.fit,e.size.cols,e.size.rows]),b.useEffect(()=>{if(e.mode!=="terminal"||e.fit!=="container"||!n.current)return;const y=new ResizeObserver(([_])=>{var E,A;const{width:T,height:x}=_.contentRect;if(T>0&&x>0){const w=((E=h.current)==null?void 0:E.w)??e.terminal.fontSize*.6,g=((A=h.current)==null?void 0:A.h)??e.terminal.fontSize*1.2;v({cols:Math.max(1,Math.floor(T/w)),rows:Math.max(1,Math.floor(x/g))})}});return y.observe(n.current),()=>y.disconnect()},[e.mode,e.fit,e.terminal.fontSize]),b.useEffect(()=>{if(e.mode!=="terminal"||!m||!o.current)return;let y=!1;const _=o.current,T=m,x=w=>{var g;i(w),(g=e.onStatusChange)==null||g.call(e,w)},E=w=>{var g;x("error"),u(w instanceof Error?w.message:String(w)),(g=e.onError)==null||g.call(e,w)};x("loading"),u("");async function A(){try{const w=await te();if(y)return;_.innerHTML="";const g=new w.Terminal({cols:T.cols,rows:T.rows,fontSize:e.terminal.fontSize,fontFamily:e.terminal.fontFamily,theme:e.terminal.theme,disableStdin:!e.interactive,cursorBlink:e.terminal.cursorBlink,convertEol:e.terminal.convertEol});a.current=g,g.open(_);let U=g.cols,R=g.rows;if(e.fit==="container"){const S=new w.FitAddon;g.loadAddon(S),S.fit(),U=g.cols,R=g.rows,n.current&&U>0&&R>0&&(h.current={w:n.current.clientWidth/U,h:n.current.clientHeight/R})}const D=e.resolveArgv({cols:U,rows:R}),B=new TextDecoder,C=new N({args:[e.wasm.toString(),...D],env:e.env,stdout:S=>g.write(B.decode(S)),stderr:S=>g.write(B.decode(S)),onExit:S=>{var q;y||(x("exited"),(q=e.onExit)==null||q.call(e,S))}});e.interactive&&g.onData(S=>C.pushInput(S));const $=await O(e.wasm,C);if(y)return;x("running"),queueMicrotask(()=>{y||$.run().catch(S=>{y||E(S)})})}catch(w){y||E(w)}}return A(),()=>{var w;y=!0,(w=a.current)==null||w.dispose(),a.current=null}},[e.mode,m,e.wasm,e.resolveArgv,e.env,e.fit,e.interactive,e.onExit,e.onError,e.onStatusChange,e.terminal.fontSize,e.terminal.fontFamily,e.terminal.theme,e.terminal.cursorBlink,e.terminal.convertEol]),b.useEffect(()=>{if(e.mode!=="static")return;let y=!1;const _=x=>{var E;i(x),(E=e.onStatusChange)==null||E.call(e,x)};_("loading"),u(""),s(null);async function T(){var x;try{const E=new TextDecoder,A=[],w=new N({args:[e.wasm.toString(),...e.resolveArgv(e.size)],env:e.env,stdout:U=>A.push(new Uint8Array(U)),stderr:()=>{},onExit:U=>{var R;if(!y){const D=A.reduce(($,S)=>$+S.length,0),B=new Uint8Array(D);let C=0;for(const $ of A)B.set($,C),C+=$.length;s(ee(E.decode(B))),_("exited"),(R=e.onExit)==null||R.call(e,U)}}}),g=await O(e.wasm,w);if(y)return;_("running"),await g.run()}catch(E){y||(_("error"),u(E instanceof Error?E.message:String(E)),(x=e.onError)==null||x.call(e,E))}}return T(),()=>{y=!0}},[e.mode,e.wasm,e.resolveArgv,e.env,e.size.cols,e.size.rows,e.onExit,e.onError,e.onStatusChange]);const f=((F=e.terminal.theme)==null?void 0:F.background)??"#1a1b26",l=((I=e.terminal.theme)==null?void 0:I.foreground)??"#a9b1d6";return e.mode==="static"?M.jsxs("div",{className:t.className,style:{position:"relative",background:f,borderRadius:6,overflow:"hidden",...t.style},children:[r==="loading"&&M.jsx("div",{style:L,children:"Loading…"}),r==="error"&&M.jsxs("div",{style:{...L,color:"#f7768e"},children:["Error: ",c]}),d!==null&&M.jsx("pre",{style:{margin:0,padding:"0.5em",fontFamily:e.terminal.fontFamily,fontSize:e.terminal.fontSize,color:l,lineHeight:1.2,background:"transparent",overflow:"auto"},dangerouslySetInnerHTML:{__html:d}})]}):M.jsxs("div",{ref:n,className:t.className,style:{position:"relative",display:"inline-block",background:f,borderRadius:6,overflow:"hidden",...t.style},children:[M.jsx("div",{ref:o,style:{display:r==="error"?"none":void 0}}),r==="loading"&&M.jsx("div",{style:L,children:"Loading…"}),r==="error"&&M.jsxs("div",{style:{...L,color:"#f7768e"},children:["Error: ",c]})]})}const L={padding:"1rem",fontFamily:"monospace",fontSize:14,color:"#a9b1d6"};exports.TuiPreview=ue;exports.WasiBridge=N;exports.WasiExitError=H;exports.instantiateApp=O;
1
+ "use strict";var V=Object.defineProperty;var G=(e,t,n)=>t in e?V(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var R=(e,t,n)=>G(e,typeof t!="symbol"?t+"":t,n);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const T=require("react/jsx-runtime"),v=require("react");let k=null;function H(){return k||(k=Promise.resolve().then(()=>require("./ghostty-web-DkOZu5AZ.cjs")).then(async e=>(await e.init(),e))),k}const _={cols:80,rows:24},W={},$=[];function Y(e){return"wasm"in e}function C(e){const t=e??$;return typeof t=="function"?t:()=>t}function Z(e){return e.cols!==void 0||e.rows!==void 0?{fit:"none",size:{cols:Math.max(1,e.cols??_.cols),rows:Math.max(1,e.rows??_.rows)}}:{fit:"container",size:_}}let D=!1;function J(e){!e||D||(D=!0,console.warn("[tui-preview] Legacy props (`app`, `args`, `cols`, `rows`, `fontSize`, `fontFamily`, `theme`) are deprecated. Use `wasm`, `argv`, `fit`, `size`, and `terminal`."))}function K(e){var i,o,r,s,c,u,a,h,m;if(Y(e)){const y=e.fit??(e.size?"none":"container"),d=y==="none"?{cols:Math.max(1,((i=e.size)==null?void 0:i.cols)??_.cols),rows:Math.max(1,((o=e.size)==null?void 0:o.rows)??_.rows)}:{cols:Math.max(1,((r=e.size)==null?void 0:r.cols)??_.cols),rows:Math.max(1,((s=e.size)==null?void 0:s.rows)??_.rows)},E=e.mode??"terminal";return{wasm:e.wasm,env:e.env??W,interactive:E==="static"?!1:e.interactive??!0,mode:E,fit:y,size:d,terminal:{fontSize:((c=e.terminal)==null?void 0:c.fontSize)??14,fontFamily:((u=e.terminal)==null?void 0:u.fontFamily)??"monospace",cursorBlink:((a=e.terminal)==null?void 0:a.cursorBlink)??!0,convertEol:((h=e.terminal)==null?void 0:h.convertEol)??!0,theme:(m=e.terminal)==null?void 0:m.theme},resolveArgv:C(e.argv),onExit:e.onExit,onError:e.onError,onStatusChange:e.onStatusChange,usedLegacyProps:!1}}const{fit:t,size:n}=Z(e);return{wasm:e.app,env:e.env??W,interactive:e.interactive??!0,mode:"terminal",fit:t,size:n,terminal:{fontSize:e.fontSize??14,fontFamily:e.fontFamily??"monospace",cursorBlink:!0,convertEol:!0,theme:e.theme},resolveArgv:C(e.args),onExit:e.onExit,onError:e.onError,onStatusChange:e.onStatusChange,usedLegacyProps:!0}}const w=0,X=6,b=8,tt=0,L=1,et=2;class j{constructor(t){R(this,"inputQueue",[]);R(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:i}=this.opts,o=new TextEncoder,r=i.reduce((s,c)=>s+o.encode(c).length+1,0);return this.view().setUint32(t,i.length,!0),this.view().setUint32(n,r,!0),w},args_get:(t,n)=>{const i=new TextEncoder,o=this.u8(),r=this.view();let s=n;return this.opts.args.forEach((c,u)=>{const a=i.encode(c);o.set(a,s),o[s+a.length]=0,r.setUint32(t+u*4,s,!0),s+=a.length+1}),w},environ_sizes_get:(t,n)=>{const i=this.envEntries(),o=new TextEncoder,r=i.reduce((s,c)=>s+o.encode(c).length+1,0);return this.view().setUint32(t,i.length,!0),this.view().setUint32(n,r,!0),w},environ_get:(t,n)=>{const i=new TextEncoder,o=this.u8(),r=this.view();let s=n;return this.envEntries().forEach((c,u)=>{const a=i.encode(c);o.set(a,s),o[s+a.length]=0,r.setUint32(t+u*4,s,!0),s+=a.length+1}),w},fd_write:(t,n,i,o)=>{if(t!==L&&t!==et)return b;const r=this.view(),s=this.u8();let c=0;const u=[];for(let m=0;m<i;m++){const y=r.getUint32(n+m*8,!0),d=r.getUint32(n+m*8+4,!0);u.push(s.slice(y,y+d)),c+=d}const a=new Uint8Array(c);let h=0;for(const m of u)a.set(m,h),h+=m.length;return t===L?this.opts.stdout(a):this.opts.stderr(a),r.setUint32(o,c,!0),w},fd_read:(t,n,i,o)=>{if(t!==tt)return b;const r=this.inputQueue.shift();if(!r)return X;const s=this.view(),c=this.u8();let u=0;for(let a=0;a<i&&u<r.length;a++){const h=s.getUint32(n+a*8,!0),m=s.getUint32(n+a*8+4,!0),y=Math.min(m,r.length-u);c.set(r.subarray(u,u+y),h),u+=y}return s.setUint32(o,u,!0),w},poll_oneoff:(t,n,i,o)=>{const r=this.view();let s=0;for(let c=0;c<i;c++){const u=t+c*48,a=r.getUint8(u+8);if(a===0&&this.inputQueue.length>0){const h=n+s*32;r.setBigUint64(h,r.getBigUint64(u,!0),!0),r.setUint16(h+8,0,!0),r.setUint8(h+10,a),s++}}return r.setUint32(o,s,!0),w},proc_exit:t=>{throw this.opts.onExit(t),new p(t)},random_get:(t,n)=>(crypto.getRandomValues(new Uint8Array(this.memory.buffer,t,n)),w),fd_close:()=>w,fd_seek:()=>w,fd_fdstat_get:(t,n)=>(this.view().setUint8(n,t<=2?2:0),w),fd_prestat_get:()=>b,fd_prestat_dir_name:()=>b,path_open:()=>b,sched_yield:()=>w,clock_time_get:(t,n,i)=>{const o=BigInt(Date.now())*1000000n;return this.view().setBigUint64(i,o,!0),w}}}envEntries(){const t={TERM:"xterm-256color",COLORTERM:"truecolor",...this.opts.env};return Object.entries(t).map(([n,i])=>`${n}=${i}`)}}class p extends Error{constructor(t){super(`WASI exit: ${t}`),this.code=t}}const I=new Map;async function N(e,t){const n=e.toString();let i=I.get(n);if(!i){const u=await(await fetch(e)).arrayBuffer();i=await WebAssembly.compile(u),I.set(n,i)}const o={wasi_snapshot_preview1:t.imports},r=await WebAssembly.instantiate(i,o);t.attachMemory(r.exports.memory);const s=r.exports._start;if(!s)throw new Error("WASM module has no _start export");return{run:async()=>{try{s()}catch(c){if(!(c instanceof p))throw c}}}}function nt(e){var y;const t=v.useMemo(()=>K(e),[e]),n=v.useRef(null),i=v.useRef(null),o=v.useRef(null),[r,s]=v.useState("loading"),[c,u]=v.useState(""),a=v.useRef(null),[h,m]=v.useState(t.size);return v.useEffect(()=>{J(t.usedLegacyProps)},[t.usedLegacyProps]),v.useEffect(()=>{m(t.size)},[t.fit,t.size.cols,t.size.rows]),v.useEffect(()=>{if(t.fit!=="container"||!n.current)return;const d=new ResizeObserver(([E])=>{var z,A;const{width:x,height:S}=E.contentRect;if(x>0&&S>0){const f=((z=a.current)==null?void 0:z.w)??t.terminal.fontSize*.6,l=((A=a.current)==null?void 0:A.h)??t.terminal.fontSize*1.2;m({cols:Math.max(1,Math.floor(x/f)),rows:Math.max(1,Math.floor(S/l))})}});return d.observe(n.current),()=>d.disconnect()},[t.fit,t.terminal.fontSize]),v.useEffect(()=>{if(!h||!i.current)return;let d=!1;const E=i.current,x=h,S=f=>{var l;s(f),(l=t.onStatusChange)==null||l.call(t,f)},z=f=>{var l;S("error"),u(f instanceof Error?f.message:String(f)),(l=t.onError)==null||l.call(t,f)};S("loading"),u("");async function A(){try{const f=await H();if(d)return;E.innerHTML="";const l=new f.Terminal({cols:x.cols,rows:x.rows,fontSize:t.terminal.fontSize,fontFamily:t.terminal.fontFamily,theme:t.terminal.theme,disableStdin:!t.interactive,cursorBlink:t.terminal.cursorBlink,convertEol:t.terminal.convertEol});o.current=l,l.open(E),t.mode==="static"&&l.write("\x1B[?25l");let U=l.cols,M=l.rows;if(t.fit==="container"){const g=new f.FitAddon;l.loadAddon(g),g.fit(),U=l.cols,M=l.rows,n.current&&U>0&&M>0&&(a.current={w:n.current.clientWidth/U,h:n.current.clientHeight/M})}const q=t.resolveArgv({cols:U,rows:M}),B=new TextDecoder,F=new j({args:[t.wasm.toString(),...q],env:t.env,stdout:g=>l.write(B.decode(g)),stderr:g=>l.write(B.decode(g)),onExit:g=>{var P;d||(S("exited"),(P=t.onExit)==null||P.call(t,g))}});t.interactive&&l.onData(g=>F.pushInput(g));const Q=await N(t.wasm,F);if(d)return;S("running"),queueMicrotask(()=>{d||Q.run().catch(g=>{d||z(g)})})}catch(f){d||z(f)}}return A(),()=>{var f;d=!0,(f=o.current)==null||f.dispose(),o.current=null}},[t.mode,h,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.cursorBlink,t.terminal.convertEol]),T.jsxs("div",{ref:n,className:e.className,style:{position:"relative",display:"inline-block",background:((y=t.terminal.theme)==null?void 0:y.background)??"#1a1b26",borderRadius:6,overflow:"hidden",...e.style},children:[T.jsx("div",{ref:i,style:{display:r==="error"?"none":void 0}}),r==="loading"&&T.jsx("div",{style:O,children:"Loading…"}),r==="error"&&T.jsxs("div",{style:{...O,color:"#f7768e"},children:["Error: ",c]})]})}const O={padding:"1rem",fontFamily:"monospace",fontSize:14,color:"#a9b1d6"};exports.TuiPreview=nt;exports.WasiBridge=j;exports.WasiExitError=p;exports.instantiateApp=N;
package/dist/index.js CHANGED
@@ -1,178 +1,103 @@
1
- var te = Object.defineProperty;
2
- var ne = (t, e, n) => e in t ? te(t, e, { enumerable: !0, configurable: !0, writable: !0, value: n }) : t[e] = n;
3
- var O = (t, e, n) => ne(t, typeof e != "symbol" ? e + "" : e, n);
4
- import { jsxs as D, jsx as p } from "react/jsx-runtime";
5
- import { useMemo as re, useRef as P, useState as W, useEffect as C } from "react";
6
- const I = [
7
- "#000000",
8
- "#cd0000",
9
- "#00cd00",
10
- "#cdcd00",
11
- "#0000ee",
12
- "#cd00cd",
13
- "#00cdcd",
14
- "#e5e5e5",
15
- "#7f7f7f",
16
- "#ff0000",
17
- "#00ff00",
18
- "#ffff00",
19
- "#5c5cff",
20
- "#ff00ff",
21
- "#00ffff",
22
- "#ffffff"
23
- ];
24
- function V(t) {
25
- if (t < 16) return I[t];
26
- if (t < 232) {
27
- const n = t - 16, s = Math.floor(n / 36), a = Math.floor(n % 36 / 6), r = n % 6, i = (c) => c === 0 ? 0 : c * 40 + 55;
28
- return `rgb(${i(s)},${i(a)},${i(r)})`;
29
- }
30
- const e = (t - 232) * 10 + 8;
31
- return `rgb(${e},${e},${e})`;
32
- }
33
- function q(t) {
34
- return t.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1
+ var H = Object.defineProperty;
2
+ var $ = (e, t, n) => t in e ? H(e, t, { enumerable: !0, configurable: !0, writable: !0, value: n }) : e[t] = n;
3
+ var k = (e, t, n) => $(e, typeof t != "symbol" ? t + "" : t, n);
4
+ import { jsxs as D, jsx as L } from "react/jsx-runtime";
5
+ import { useMemo as Y, useRef as M, useState as R, useEffect as T } from "react";
6
+ let p = null;
7
+ function q() {
8
+ return p || (p = import("./ghostty-web-BfBVpf8G.js").then(async (e) => (await e.init(), e))), p;
35
9
  }
36
- function ie(t) {
37
- let e = null, n = null, s = !1, a = !1, r = !1;
38
- const i = [];
39
- let c = "", u = "";
40
- function h() {
41
- const o = [];
42
- return e && o.push(`color:${e}`), n && o.push(`background-color:${n}`), s && o.push("font-weight:bold"), a && o.push("font-style:italic"), r && o.push("text-decoration:underline"), o.join(";");
43
- }
44
- function m() {
45
- c && (i.push(u ? `<span style="${u}">${q(c)}</span>` : q(c)), c = "");
46
- }
47
- function v(o) {
48
- m();
49
- let f = 0;
50
- for (; f < o.length; ) {
51
- const l = o[f];
52
- l === 0 ? (e = null, n = null, s = !1, a = !1, r = !1) : l === 1 ? s = !0 : l === 22 ? s = !1 : l === 3 ? a = !0 : l === 23 ? a = !1 : l === 4 ? r = !0 : l === 24 ? r = !1 : l >= 30 && l <= 37 ? e = I[l - 30] : l === 38 ? o[f + 1] === 2 && f + 4 < o.length ? (e = `rgb(${o[f + 2]},${o[f + 3]},${o[f + 4]})`, f += 4) : o[f + 1] === 5 && f + 2 < o.length && (e = V(o[f + 2]), f += 2) : l === 39 ? e = null : l >= 40 && l <= 47 ? n = I[l - 40] : l === 48 ? o[f + 1] === 2 && f + 4 < o.length ? (n = `rgb(${o[f + 2]},${o[f + 3]},${o[f + 4]})`, f += 4) : o[f + 1] === 5 && f + 2 < o.length && (n = V(o[f + 2]), f += 2) : l === 49 ? n = null : l >= 90 && l <= 97 ? e = I[l - 82] : l >= 100 && l <= 107 && (n = I[l - 92]), f++;
53
- }
54
- u = h();
55
- }
56
- let d = 0;
57
- for (; d < t.length; ) {
58
- const o = t[d];
59
- if (o === "\x1B" && d + 1 < t.length && t[d + 1] === "[") {
60
- const f = d + 2;
61
- let l = f;
62
- for (; l < t.length && (t.charCodeAt(l) < 64 || t.charCodeAt(l) > 126); )
63
- l++;
64
- if (l < t.length) {
65
- if (t[l] === "m") {
66
- const $ = t.slice(f, l), L = $ ? $.split(";").map((y) => parseInt(y, 10) || 0) : [0];
67
- v(L);
68
- }
69
- d = l + 1;
70
- } else
71
- d++;
72
- } else if (o === "\r")
73
- d++;
74
- else {
75
- const f = h();
76
- f !== u && (m(), u = f), c += o, d++;
77
- }
78
- }
79
- return m(), i.join("");
10
+ const S = { cols: 80, rows: 24 }, P = {}, Z = [];
11
+ function J(e) {
12
+ return "wasm" in e;
80
13
  }
81
- let j = null;
82
- function oe() {
83
- return j || (j = import("./ghostty-web-BfBVpf8G.js").then(async (t) => (await t.init(), t))), j;
14
+ function I(e) {
15
+ const t = e ?? Z;
16
+ return typeof t == "function" ? t : () => t;
84
17
  }
85
- const T = { cols: 80, rows: 24 }, G = {}, se = [];
86
- function ce(t) {
87
- return "wasm" in t;
88
- }
89
- function Y(t) {
90
- const e = t ?? se;
91
- return typeof e == "function" ? e : () => e;
92
- }
93
- function le(t) {
94
- return t.cols !== void 0 || t.rows !== void 0 ? {
18
+ function K(e) {
19
+ return e.cols !== void 0 || e.rows !== void 0 ? {
95
20
  fit: "none",
96
21
  size: {
97
- cols: Math.max(1, t.cols ?? T.cols),
98
- rows: Math.max(1, t.rows ?? T.rows)
22
+ cols: Math.max(1, e.cols ?? S.cols),
23
+ rows: Math.max(1, e.rows ?? S.rows)
99
24
  }
100
- } : { fit: "container", size: T };
25
+ } : { fit: "container", size: S };
101
26
  }
102
- let Z = !1;
103
- function ae(t) {
104
- !t || Z || (Z = !0, console.warn(
27
+ let W = !1;
28
+ function X(e) {
29
+ !e || W || (W = !0, console.warn(
105
30
  "[tui-preview] Legacy props (`app`, `args`, `cols`, `rows`, `fontSize`, `fontFamily`, `theme`) are deprecated. Use `wasm`, `argv`, `fit`, `size`, and `terminal`."
106
31
  ));
107
32
  }
108
- function ue(t) {
109
- var s, a, r, i, c, u, h, m, v;
110
- if (ce(t)) {
111
- const d = t.fit ?? (t.size ? "none" : "container"), o = d === "none" ? {
112
- cols: Math.max(1, ((s = t.size) == null ? void 0 : s.cols) ?? T.cols),
113
- rows: Math.max(1, ((a = t.size) == null ? void 0 : a.rows) ?? T.rows)
33
+ function tt(e) {
34
+ var i, s, r, o, c, u, a, h, m;
35
+ if (J(e)) {
36
+ const v = e.fit ?? (e.size ? "none" : "container"), w = v === "none" ? {
37
+ cols: Math.max(1, ((i = e.size) == null ? void 0 : i.cols) ?? S.cols),
38
+ rows: Math.max(1, ((s = e.size) == null ? void 0 : s.rows) ?? S.rows)
114
39
  } : {
115
- cols: Math.max(1, ((r = t.size) == null ? void 0 : r.cols) ?? T.cols),
116
- rows: Math.max(1, ((i = t.size) == null ? void 0 : i.rows) ?? T.rows)
117
- };
40
+ cols: Math.max(1, ((r = e.size) == null ? void 0 : r.cols) ?? S.cols),
41
+ rows: Math.max(1, ((o = e.size) == null ? void 0 : o.rows) ?? S.rows)
42
+ }, y = e.mode ?? "terminal";
118
43
  return {
119
- wasm: t.wasm,
120
- env: t.env ?? G,
121
- interactive: t.interactive ?? !0,
122
- mode: t.mode ?? "terminal",
123
- fit: d,
124
- size: o,
44
+ wasm: e.wasm,
45
+ env: e.env ?? P,
46
+ interactive: y === "static" ? !1 : e.interactive ?? !0,
47
+ mode: y,
48
+ fit: v,
49
+ size: w,
125
50
  terminal: {
126
- fontSize: ((c = t.terminal) == null ? void 0 : c.fontSize) ?? 14,
127
- fontFamily: ((u = t.terminal) == null ? void 0 : u.fontFamily) ?? "monospace",
128
- cursorBlink: ((h = t.terminal) == null ? void 0 : h.cursorBlink) ?? !0,
129
- convertEol: ((m = t.terminal) == null ? void 0 : m.convertEol) ?? !0,
130
- theme: (v = t.terminal) == null ? void 0 : v.theme
51
+ fontSize: ((c = e.terminal) == null ? void 0 : c.fontSize) ?? 14,
52
+ fontFamily: ((u = e.terminal) == null ? void 0 : u.fontFamily) ?? "monospace",
53
+ cursorBlink: ((a = e.terminal) == null ? void 0 : a.cursorBlink) ?? !0,
54
+ convertEol: ((h = e.terminal) == null ? void 0 : h.convertEol) ?? !0,
55
+ theme: (m = e.terminal) == null ? void 0 : m.theme
131
56
  },
132
- resolveArgv: Y(t.argv),
133
- onExit: t.onExit,
134
- onError: t.onError,
135
- onStatusChange: t.onStatusChange,
57
+ resolveArgv: I(e.argv),
58
+ onExit: e.onExit,
59
+ onError: e.onError,
60
+ onStatusChange: e.onStatusChange,
136
61
  usedLegacyProps: !1
137
62
  };
138
63
  }
139
- const { fit: e, size: n } = le(t);
64
+ const { fit: t, size: n } = K(e);
140
65
  return {
141
- wasm: t.app,
142
- env: t.env ?? G,
143
- interactive: t.interactive ?? !0,
66
+ wasm: e.app,
67
+ env: e.env ?? P,
68
+ interactive: e.interactive ?? !0,
144
69
  mode: "terminal",
145
- fit: e,
70
+ fit: t,
146
71
  size: n,
147
72
  terminal: {
148
- fontSize: t.fontSize ?? 14,
149
- fontFamily: t.fontFamily ?? "monospace",
73
+ fontSize: e.fontSize ?? 14,
74
+ fontFamily: e.fontFamily ?? "monospace",
150
75
  cursorBlink: !0,
151
76
  convertEol: !0,
152
- theme: t.theme
77
+ theme: e.theme
153
78
  },
154
- resolveArgv: Y(t.args),
155
- onExit: t.onExit,
156
- onError: t.onError,
157
- onStatusChange: t.onStatusChange,
79
+ resolveArgv: I(e.args),
80
+ onExit: e.onExit,
81
+ onError: e.onError,
82
+ onStatusChange: e.onStatusChange,
158
83
  usedLegacyProps: !0
159
84
  };
160
85
  }
161
- const z = 0, fe = 6, B = 8, he = 0, J = 1, de = 2;
162
- class K {
163
- constructor(e) {
164
- O(this, "inputQueue", []);
165
- O(this, "memory");
166
- this.opts = e;
86
+ const d = 0, et = 6, x = 8, nt = 0, O = 1, rt = 2;
87
+ class it {
88
+ constructor(t) {
89
+ k(this, "inputQueue", []);
90
+ k(this, "memory");
91
+ this.opts = t;
167
92
  }
168
93
  /** Push keyboard data from the terminal into the app's stdin */
169
- pushInput(e) {
170
- const n = typeof e == "string" ? new TextEncoder().encode(e) : e;
94
+ pushInput(t) {
95
+ const n = typeof t == "string" ? new TextEncoder().encode(t) : t;
171
96
  this.inputQueue.push(n);
172
97
  }
173
98
  /** Attach the WASM instance's memory after instantiation */
174
- attachMemory(e) {
175
- this.memory = e;
99
+ attachMemory(t) {
100
+ this.memory = t;
176
101
  }
177
102
  view() {
178
103
  return new DataView(this.memory.buffer);
@@ -183,317 +108,240 @@ class K {
183
108
  // ── WASI imports object ────────────────────────────────────────────────
184
109
  get imports() {
185
110
  return {
186
- args_sizes_get: (e, n) => {
187
- const { args: s } = this.opts, a = new TextEncoder(), r = s.reduce((i, c) => i + a.encode(c).length + 1, 0);
188
- return this.view().setUint32(e, s.length, !0), this.view().setUint32(n, r, !0), z;
111
+ args_sizes_get: (t, n) => {
112
+ const { args: i } = this.opts, s = new TextEncoder(), r = i.reduce((o, c) => o + s.encode(c).length + 1, 0);
113
+ return this.view().setUint32(t, i.length, !0), this.view().setUint32(n, r, !0), d;
189
114
  },
190
- args_get: (e, n) => {
191
- const s = new TextEncoder(), a = this.u8(), r = this.view();
192
- let i = n;
115
+ args_get: (t, n) => {
116
+ const i = new TextEncoder(), s = this.u8(), r = this.view();
117
+ let o = n;
193
118
  return this.opts.args.forEach((c, u) => {
194
- const h = s.encode(c);
195
- a.set(h, i), a[i + h.length] = 0, r.setUint32(e + u * 4, i, !0), i += h.length + 1;
196
- }), z;
119
+ const a = i.encode(c);
120
+ s.set(a, o), s[o + a.length] = 0, r.setUint32(t + u * 4, o, !0), o += a.length + 1;
121
+ }), d;
197
122
  },
198
- environ_sizes_get: (e, n) => {
199
- const s = this.envEntries(), a = new TextEncoder(), r = s.reduce((i, c) => i + a.encode(c).length + 1, 0);
200
- return this.view().setUint32(e, s.length, !0), this.view().setUint32(n, r, !0), z;
123
+ environ_sizes_get: (t, n) => {
124
+ const i = this.envEntries(), s = new TextEncoder(), r = i.reduce((o, c) => o + s.encode(c).length + 1, 0);
125
+ return this.view().setUint32(t, i.length, !0), this.view().setUint32(n, r, !0), d;
201
126
  },
202
- environ_get: (e, n) => {
203
- const s = new TextEncoder(), a = this.u8(), r = this.view();
204
- let i = n;
127
+ environ_get: (t, n) => {
128
+ const i = new TextEncoder(), s = this.u8(), r = this.view();
129
+ let o = n;
205
130
  return this.envEntries().forEach((c, u) => {
206
- const h = s.encode(c);
207
- a.set(h, i), a[i + h.length] = 0, r.setUint32(e + u * 4, i, !0), i += h.length + 1;
208
- }), z;
131
+ const a = i.encode(c);
132
+ s.set(a, o), s[o + a.length] = 0, r.setUint32(t + u * 4, o, !0), o += a.length + 1;
133
+ }), d;
209
134
  },
210
- fd_write: (e, n, s, a) => {
211
- if (e !== J && e !== de) return B;
212
- const r = this.view(), i = this.u8();
135
+ fd_write: (t, n, i, s) => {
136
+ if (t !== O && t !== rt) return x;
137
+ const r = this.view(), o = this.u8();
213
138
  let c = 0;
214
139
  const u = [];
215
- for (let v = 0; v < s; v++) {
216
- const d = r.getUint32(n + v * 8, !0), o = r.getUint32(n + v * 8 + 4, !0);
217
- u.push(i.slice(d, d + o)), c += o;
140
+ for (let m = 0; m < i; m++) {
141
+ const v = r.getUint32(n + m * 8, !0), w = r.getUint32(n + m * 8 + 4, !0);
142
+ u.push(o.slice(v, v + w)), c += w;
218
143
  }
219
- const h = new Uint8Array(c);
220
- let m = 0;
221
- for (const v of u)
222
- h.set(v, m), m += v.length;
223
- return e === J ? this.opts.stdout(h) : this.opts.stderr(h), r.setUint32(a, c, !0), z;
144
+ const a = new Uint8Array(c);
145
+ let h = 0;
146
+ for (const m of u)
147
+ a.set(m, h), h += m.length;
148
+ return t === O ? this.opts.stdout(a) : this.opts.stderr(a), r.setUint32(s, c, !0), d;
224
149
  },
225
- fd_read: (e, n, s, a) => {
226
- if (e !== he) return B;
150
+ fd_read: (t, n, i, s) => {
151
+ if (t !== nt) return x;
227
152
  const r = this.inputQueue.shift();
228
- if (!r) return fe;
229
- const i = this.view(), c = this.u8();
153
+ if (!r) return et;
154
+ const o = this.view(), c = this.u8();
230
155
  let u = 0;
231
- for (let h = 0; h < s && u < r.length; h++) {
232
- const m = i.getUint32(n + h * 8, !0), v = i.getUint32(n + h * 8 + 4, !0), d = Math.min(v, r.length - u);
233
- c.set(r.subarray(u, u + d), m), u += d;
156
+ for (let a = 0; a < i && u < r.length; a++) {
157
+ const h = o.getUint32(n + a * 8, !0), m = o.getUint32(n + a * 8 + 4, !0), v = Math.min(m, r.length - u);
158
+ c.set(r.subarray(u, u + v), h), u += v;
234
159
  }
235
- return i.setUint32(a, u, !0), z;
160
+ return o.setUint32(s, u, !0), d;
236
161
  },
237
- poll_oneoff: (e, n, s, a) => {
162
+ poll_oneoff: (t, n, i, s) => {
238
163
  const r = this.view();
239
- let i = 0;
240
- for (let c = 0; c < s; c++) {
241
- const u = e + c * 48, h = r.getUint8(u + 8);
242
- if (h === 0 && this.inputQueue.length > 0) {
243
- const m = n + i * 32;
244
- r.setBigUint64(m, r.getBigUint64(u, !0), !0), r.setUint16(m + 8, 0, !0), r.setUint8(m + 10, h), i++;
164
+ let o = 0;
165
+ for (let c = 0; c < i; c++) {
166
+ const u = t + c * 48, a = r.getUint8(u + 8);
167
+ if (a === 0 && this.inputQueue.length > 0) {
168
+ const h = n + o * 32;
169
+ r.setBigUint64(h, r.getBigUint64(u, !0), !0), r.setUint16(h + 8, 0, !0), r.setUint8(h + 10, a), o++;
245
170
  }
246
171
  }
247
- return r.setUint32(a, i, !0), z;
172
+ return r.setUint32(s, o, !0), d;
248
173
  },
249
- proc_exit: (e) => {
250
- throw this.opts.onExit(e), new ee(e);
174
+ proc_exit: (t) => {
175
+ throw this.opts.onExit(t), new Q(t);
251
176
  },
252
- random_get: (e, n) => (crypto.getRandomValues(new Uint8Array(this.memory.buffer, e, n)), z),
177
+ random_get: (t, n) => (crypto.getRandomValues(new Uint8Array(this.memory.buffer, t, n)), d),
253
178
  // Stubs for calls TUI apps may make but we don't need to implement.
254
- fd_close: () => z,
255
- fd_seek: () => z,
256
- fd_fdstat_get: (e, n) => (this.view().setUint8(n, e <= 2 ? 2 : 0), z),
257
- fd_prestat_get: () => B,
258
- fd_prestat_dir_name: () => B,
259
- path_open: () => B,
260
- sched_yield: () => z,
261
- clock_time_get: (e, n, s) => {
262
- const a = BigInt(Date.now()) * 1000000n;
263
- return this.view().setBigUint64(s, a, !0), z;
179
+ fd_close: () => d,
180
+ fd_seek: () => d,
181
+ fd_fdstat_get: (t, n) => (this.view().setUint8(n, t <= 2 ? 2 : 0), d),
182
+ fd_prestat_get: () => x,
183
+ fd_prestat_dir_name: () => x,
184
+ path_open: () => x,
185
+ sched_yield: () => d,
186
+ clock_time_get: (t, n, i) => {
187
+ const s = BigInt(Date.now()) * 1000000n;
188
+ return this.view().setBigUint64(i, s, !0), d;
264
189
  }
265
190
  };
266
191
  }
267
192
  envEntries() {
268
- const e = {
193
+ const t = {
269
194
  TERM: "xterm-256color",
270
195
  COLORTERM: "truecolor",
271
196
  ...this.opts.env
272
197
  };
273
- return Object.entries(e).map(([n, s]) => `${n}=${s}`);
198
+ return Object.entries(t).map(([n, i]) => `${n}=${i}`);
274
199
  }
275
200
  }
276
- class ee extends Error {
277
- constructor(e) {
278
- super(`WASI exit: ${e}`), this.code = e;
201
+ class Q extends Error {
202
+ constructor(t) {
203
+ super(`WASI exit: ${t}`), this.code = t;
279
204
  }
280
205
  }
281
- async function X(t, e) {
282
- const s = await (await fetch(t)).arrayBuffer(), a = await WebAssembly.compile(s), r = {
283
- wasi_snapshot_preview1: e.imports
284
- }, i = await WebAssembly.instantiate(a, r);
285
- e.attachMemory(i.exports.memory);
286
- const c = i.exports._start;
287
- if (!c) throw new Error("WASM module has no _start export");
206
+ const N = /* @__PURE__ */ new Map();
207
+ async function ot(e, t) {
208
+ const n = e.toString();
209
+ let i = N.get(n);
210
+ if (!i) {
211
+ const u = await (await fetch(e)).arrayBuffer();
212
+ i = await WebAssembly.compile(u), N.set(n, i);
213
+ }
214
+ const s = {
215
+ wasi_snapshot_preview1: t.imports
216
+ }, r = await WebAssembly.instantiate(i, s);
217
+ t.attachMemory(r.exports.memory);
218
+ const o = r.exports._start;
219
+ if (!o) throw new Error("WASM module has no _start export");
288
220
  return {
289
221
  run: async () => {
290
222
  try {
291
- c();
292
- } catch (u) {
293
- if (!(u instanceof ee)) throw u;
223
+ o();
224
+ } catch (c) {
225
+ if (!(c instanceof Q)) throw c;
294
226
  }
295
227
  }
296
228
  };
297
229
  }
298
- function ve(t) {
299
- var $, L;
300
- const e = re(() => ue(t), [t]), n = P(null), s = P(null), a = P(null), [r, i] = W("loading"), [c, u] = W(""), h = P(null), [m, v] = W(e.size), [d, o] = W(null);
301
- C(() => {
302
- ae(e.usedLegacyProps);
303
- }, [e.usedLegacyProps]), C(() => {
304
- e.mode === "terminal" && v(e.size);
305
- }, [e.mode, e.fit, e.size.cols, e.size.rows]), C(() => {
306
- if (e.mode !== "terminal" || e.fit !== "container" || !n.current) return;
307
- const y = new ResizeObserver(([_]) => {
308
- var E, x;
309
- const { width: U, height: b } = _.contentRect;
310
- if (U > 0 && b > 0) {
311
- const w = ((E = h.current) == null ? void 0 : E.w) ?? e.terminal.fontSize * 0.6, g = ((x = h.current) == null ? void 0 : x.h) ?? e.terminal.fontSize * 1.2;
312
- v({
313
- cols: Math.max(1, Math.floor(U / w)),
314
- rows: Math.max(1, Math.floor(b / g))
230
+ function ut(e) {
231
+ var v;
232
+ const t = Y(() => tt(e), [e]), n = M(null), i = M(null), s = M(null), [r, o] = R("loading"), [c, u] = R(""), a = M(null), [h, m] = R(t.size);
233
+ return T(() => {
234
+ X(t.usedLegacyProps);
235
+ }, [t.usedLegacyProps]), T(() => {
236
+ m(t.size);
237
+ }, [t.fit, t.size.cols, t.size.rows]), T(() => {
238
+ if (t.fit !== "container" || !n.current) return;
239
+ const w = new ResizeObserver(([y]) => {
240
+ var z, b;
241
+ const { width: _, height: E } = y.contentRect;
242
+ if (_ > 0 && E > 0) {
243
+ const f = ((z = a.current) == null ? void 0 : z.w) ?? t.terminal.fontSize * 0.6, l = ((b = a.current) == null ? void 0 : b.h) ?? t.terminal.fontSize * 1.2;
244
+ m({
245
+ cols: Math.max(1, Math.floor(_ / f)),
246
+ rows: Math.max(1, Math.floor(E / l))
315
247
  });
316
248
  }
317
249
  });
318
- return y.observe(n.current), () => y.disconnect();
319
- }, [e.mode, e.fit, e.terminal.fontSize]), C(() => {
320
- if (e.mode !== "terminal" || !m || !s.current) return;
321
- let y = !1;
322
- const _ = s.current, U = m, b = (w) => {
323
- var g;
324
- i(w), (g = e.onStatusChange) == null || g.call(e, w);
325
- }, E = (w) => {
326
- var g;
327
- b("error"), u(w instanceof Error ? w.message : String(w)), (g = e.onError) == null || g.call(e, w);
250
+ return w.observe(n.current), () => w.disconnect();
251
+ }, [t.fit, t.terminal.fontSize]), T(() => {
252
+ if (!h || !i.current) return;
253
+ let w = !1;
254
+ const y = i.current, _ = h, E = (f) => {
255
+ var l;
256
+ o(f), (l = t.onStatusChange) == null || l.call(t, f);
257
+ }, z = (f) => {
258
+ var l;
259
+ E("error"), u(f instanceof Error ? f.message : String(f)), (l = t.onError) == null || l.call(t, f);
328
260
  };
329
- b("loading"), u("");
330
- async function x() {
261
+ E("loading"), u("");
262
+ async function b() {
331
263
  try {
332
- const w = await oe();
333
- if (y) return;
334
- _.innerHTML = "";
335
- const g = new w.Terminal({
336
- cols: U.cols,
337
- rows: U.rows,
338
- fontSize: e.terminal.fontSize,
339
- fontFamily: e.terminal.fontFamily,
340
- theme: e.terminal.theme,
341
- disableStdin: !e.interactive,
342
- cursorBlink: e.terminal.cursorBlink,
343
- convertEol: e.terminal.convertEol
264
+ const f = await q();
265
+ if (w) return;
266
+ y.innerHTML = "";
267
+ const l = new f.Terminal({
268
+ cols: _.cols,
269
+ rows: _.rows,
270
+ fontSize: t.terminal.fontSize,
271
+ fontFamily: t.terminal.fontFamily,
272
+ theme: t.terminal.theme,
273
+ disableStdin: !t.interactive,
274
+ cursorBlink: t.terminal.cursorBlink,
275
+ convertEol: t.terminal.convertEol
344
276
  });
345
- a.current = g, g.open(_);
346
- let A = g.cols, M = g.rows;
347
- if (e.fit === "container") {
348
- const S = new w.FitAddon();
349
- g.loadAddon(S), S.fit(), A = g.cols, M = g.rows, n.current && A > 0 && M > 0 && (h.current = {
277
+ s.current = l, l.open(y), t.mode === "static" && l.write("\x1B[?25l");
278
+ let A = l.cols, U = l.rows;
279
+ if (t.fit === "container") {
280
+ const g = new f.FitAddon();
281
+ l.loadAddon(g), g.fit(), A = l.cols, U = l.rows, n.current && A > 0 && U > 0 && (a.current = {
350
282
  w: n.current.clientWidth / A,
351
- h: n.current.clientHeight / M
283
+ h: n.current.clientHeight / U
352
284
  });
353
285
  }
354
- const H = e.resolveArgv({ cols: A, rows: M }), R = new TextDecoder(), F = new K({
355
- args: [e.wasm.toString(), ...H],
356
- env: e.env,
357
- stdout: (S) => g.write(R.decode(S)),
358
- stderr: (S) => g.write(R.decode(S)),
359
- onExit: (S) => {
360
- var Q;
361
- y || (b("exited"), (Q = e.onExit) == null || Q.call(e, S));
286
+ const V = t.resolveArgv({ cols: A, rows: U }), F = new TextDecoder(), B = new it({
287
+ args: [t.wasm.toString(), ...V],
288
+ env: t.env,
289
+ stdout: (g) => l.write(F.decode(g)),
290
+ stderr: (g) => l.write(F.decode(g)),
291
+ onExit: (g) => {
292
+ var C;
293
+ w || (E("exited"), (C = t.onExit) == null || C.call(t, g));
362
294
  }
363
295
  });
364
- e.interactive && g.onData((S) => F.pushInput(S));
365
- const k = await X(e.wasm, F);
366
- if (y) return;
367
- b("running"), queueMicrotask(() => {
368
- y || k.run().catch((S) => {
369
- y || E(S);
296
+ t.interactive && l.onData((g) => B.pushInput(g));
297
+ const G = await ot(t.wasm, B);
298
+ if (w) return;
299
+ E("running"), queueMicrotask(() => {
300
+ w || G.run().catch((g) => {
301
+ w || z(g);
370
302
  });
371
303
  });
372
- } catch (w) {
373
- y || E(w);
304
+ } catch (f) {
305
+ w || z(f);
374
306
  }
375
307
  }
376
- return x(), () => {
377
- var w;
378
- y = !0, (w = a.current) == null || w.dispose(), a.current = null;
308
+ return b(), () => {
309
+ var f;
310
+ w = !0, (f = s.current) == null || f.dispose(), s.current = null;
379
311
  };
380
312
  }, [
381
- e.mode,
382
- m,
383
- e.wasm,
384
- e.resolveArgv,
385
- e.env,
386
- e.fit,
387
- e.interactive,
388
- e.onExit,
389
- e.onError,
390
- e.onStatusChange,
391
- e.terminal.fontSize,
392
- e.terminal.fontFamily,
393
- e.terminal.theme,
394
- e.terminal.cursorBlink,
395
- e.terminal.convertEol
396
- ]), C(() => {
397
- if (e.mode !== "static") return;
398
- let y = !1;
399
- const _ = (b) => {
400
- var E;
401
- i(b), (E = e.onStatusChange) == null || E.call(e, b);
402
- };
403
- _("loading"), u(""), o(null);
404
- async function U() {
405
- var b;
406
- try {
407
- const E = new TextDecoder(), x = [], w = new K({
408
- args: [e.wasm.toString(), ...e.resolveArgv(e.size)],
409
- env: e.env,
410
- stdout: (A) => x.push(new Uint8Array(A)),
411
- stderr: () => {
412
- },
413
- onExit: (A) => {
414
- var M;
415
- if (!y) {
416
- const H = x.reduce((k, S) => k + S.length, 0), R = new Uint8Array(H);
417
- let F = 0;
418
- for (const k of x)
419
- R.set(k, F), F += k.length;
420
- o(ie(E.decode(R))), _("exited"), (M = e.onExit) == null || M.call(e, A);
421
- }
422
- }
423
- }), g = await X(e.wasm, w);
424
- if (y) return;
425
- _("running"), await g.run();
426
- } catch (E) {
427
- y || (_("error"), u(E instanceof Error ? E.message : String(E)), (b = e.onError) == null || b.call(e, E));
428
- }
429
- }
430
- return U(), () => {
431
- y = !0;
432
- };
433
- }, [
434
- e.mode,
435
- e.wasm,
436
- e.resolveArgv,
437
- e.env,
438
- e.size.cols,
439
- e.size.rows,
440
- e.onExit,
441
- e.onError,
442
- e.onStatusChange
443
- ]);
444
- const f = (($ = e.terminal.theme) == null ? void 0 : $.background) ?? "#1a1b26", l = ((L = e.terminal.theme) == null ? void 0 : L.foreground) ?? "#a9b1d6";
445
- return e.mode === "static" ? /* @__PURE__ */ D(
446
- "div",
447
- {
448
- className: t.className,
449
- style: {
450
- position: "relative",
451
- background: f,
452
- borderRadius: 6,
453
- overflow: "hidden",
454
- ...t.style
455
- },
456
- children: [
457
- r === "loading" && /* @__PURE__ */ p("div", { style: N, children: "Loading…" }),
458
- r === "error" && /* @__PURE__ */ D("div", { style: { ...N, color: "#f7768e" }, children: [
459
- "Error: ",
460
- c
461
- ] }),
462
- d !== null && /* @__PURE__ */ p(
463
- "pre",
464
- {
465
- style: {
466
- margin: 0,
467
- padding: "0.5em",
468
- fontFamily: e.terminal.fontFamily,
469
- fontSize: e.terminal.fontSize,
470
- color: l,
471
- lineHeight: 1.2,
472
- background: "transparent",
473
- overflow: "auto"
474
- },
475
- dangerouslySetInnerHTML: { __html: d }
476
- }
477
- )
478
- ]
479
- }
480
- ) : /* @__PURE__ */ D(
313
+ t.mode,
314
+ h,
315
+ t.wasm,
316
+ t.resolveArgv,
317
+ t.env,
318
+ t.fit,
319
+ t.interactive,
320
+ t.onExit,
321
+ t.onError,
322
+ t.onStatusChange,
323
+ t.terminal.fontSize,
324
+ t.terminal.fontFamily,
325
+ t.terminal.theme,
326
+ t.terminal.cursorBlink,
327
+ t.terminal.convertEol
328
+ ]), /* @__PURE__ */ D(
481
329
  "div",
482
330
  {
483
331
  ref: n,
484
- className: t.className,
332
+ className: e.className,
485
333
  style: {
486
334
  position: "relative",
487
335
  display: "inline-block",
488
- background: f,
336
+ background: ((v = t.terminal.theme) == null ? void 0 : v.background) ?? "#1a1b26",
489
337
  borderRadius: 6,
490
338
  overflow: "hidden",
491
- ...t.style
339
+ ...e.style
492
340
  },
493
341
  children: [
494
- /* @__PURE__ */ p("div", { ref: s, style: { display: r === "error" ? "none" : void 0 } }),
495
- r === "loading" && /* @__PURE__ */ p("div", { style: N, children: "Loading…" }),
496
- r === "error" && /* @__PURE__ */ D("div", { style: { ...N, color: "#f7768e" }, children: [
342
+ /* @__PURE__ */ L("div", { ref: i, style: { display: r === "error" ? "none" : void 0 } }),
343
+ r === "loading" && /* @__PURE__ */ L("div", { style: j, children: "Loading…" }),
344
+ r === "error" && /* @__PURE__ */ D("div", { style: { ...j, color: "#f7768e" }, children: [
497
345
  "Error: ",
498
346
  c
499
347
  ] })
@@ -501,15 +349,15 @@ function ve(t) {
501
349
  }
502
350
  );
503
351
  }
504
- const N = {
352
+ const j = {
505
353
  padding: "1rem",
506
354
  fontFamily: "monospace",
507
355
  fontSize: 14,
508
356
  color: "#a9b1d6"
509
357
  };
510
358
  export {
511
- ve as TuiPreview,
512
- K as WasiBridge,
513
- ee as WasiExitError,
514
- X as instantiateApp
359
+ ut as TuiPreview,
360
+ it as WasiBridge,
361
+ Q as WasiExitError,
362
+ ot as instantiateApp
515
363
  };
package/package.json CHANGED
@@ -1,7 +1,15 @@
1
1
  {
2
2
  "name": "@dkkoval/tui-preview",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "React component for embedding interactive TUI apps via ghostty-web",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/dmk/tui-preview"
8
+ },
9
+ "homepage": "https://github.com/dmk/tui-preview#readme",
10
+ "bugs": {
11
+ "url": "https://github.com/dmk/tui-preview/issues"
12
+ },
5
13
  "type": "module",
6
14
  "license": "MIT",
7
15
  "sideEffects": false,
@@ -1,15 +0,0 @@
1
- /**
2
- * Minimal ANSI SGR → HTML converter.
3
- *
4
- * Handles the subset used by typical wasm32-wasi TUI apps:
5
- * - Reset (0)
6
- * - Bold, italic, underline (1/3/4 and their off codes)
7
- * - Standard 16-color fg/bg (30–37, 39, 40–47, 49, 90–97, 100–107)
8
- * - 256-color fg/bg (38;5;N / 48;5;N)
9
- * - 24-bit truecolor fg/bg (38;2;R;G;B / 48;2;R;G;B)
10
- *
11
- * Non-SGR CSI sequences (cursor movement etc.) are silently dropped,
12
- * which is correct for static capture where only the final text matters.
13
- */
14
- /** Convert ANSI escape codes to an HTML string of styled `<span>` elements. */
15
- export declare function ansiToHtml(input: string): string;
package/dist/core/ansi.js DELETED
@@ -1,181 +0,0 @@
1
- /**
2
- * Minimal ANSI SGR → HTML converter.
3
- *
4
- * Handles the subset used by typical wasm32-wasi TUI apps:
5
- * - Reset (0)
6
- * - Bold, italic, underline (1/3/4 and their off codes)
7
- * - Standard 16-color fg/bg (30–37, 39, 40–47, 49, 90–97, 100–107)
8
- * - 256-color fg/bg (38;5;N / 48;5;N)
9
- * - 24-bit truecolor fg/bg (38;2;R;G;B / 48;2;R;G;B)
10
- *
11
- * Non-SGR CSI sequences (cursor movement etc.) are silently dropped,
12
- * which is correct for static capture where only the final text matters.
13
- */
14
- // Standard xterm 16-color palette
15
- const ANSI16 = [
16
- "#000000", "#cd0000", "#00cd00", "#cdcd00",
17
- "#0000ee", "#cd00cd", "#00cdcd", "#e5e5e5",
18
- "#7f7f7f", "#ff0000", "#00ff00", "#ffff00",
19
- "#5c5cff", "#ff00ff", "#00ffff", "#ffffff",
20
- ];
21
- function color256(n) {
22
- if (n < 16)
23
- return ANSI16[n];
24
- if (n < 232) {
25
- const i = n - 16;
26
- const r = Math.floor(i / 36);
27
- const g = Math.floor((i % 36) / 6);
28
- const b = i % 6;
29
- const v = (x) => (x === 0 ? 0 : x * 40 + 55);
30
- return `rgb(${v(r)},${v(g)},${v(b)})`;
31
- }
32
- const grey = (n - 232) * 10 + 8;
33
- return `rgb(${grey},${grey},${grey})`;
34
- }
35
- function escHtml(s) {
36
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
37
- }
38
- /** Convert ANSI escape codes to an HTML string of styled `<span>` elements. */
39
- export function ansiToHtml(input) {
40
- // Current SGR state
41
- let fg = null;
42
- let bg = null;
43
- let bold = false;
44
- let italic = false;
45
- let underline = false;
46
- const result = [];
47
- let buf = "";
48
- let bufStyle = "";
49
- function buildStyle() {
50
- const parts = [];
51
- if (fg)
52
- parts.push(`color:${fg}`);
53
- if (bg)
54
- parts.push(`background-color:${bg}`);
55
- if (bold)
56
- parts.push("font-weight:bold");
57
- if (italic)
58
- parts.push("font-style:italic");
59
- if (underline)
60
- parts.push("text-decoration:underline");
61
- return parts.join(";");
62
- }
63
- function flush() {
64
- if (!buf)
65
- return;
66
- result.push(bufStyle ? `<span style="${bufStyle}">${escHtml(buf)}</span>` : escHtml(buf));
67
- buf = "";
68
- }
69
- function applySgr(params) {
70
- flush();
71
- let i = 0;
72
- while (i < params.length) {
73
- const p = params[i];
74
- if (p === 0) {
75
- fg = null;
76
- bg = null;
77
- bold = false;
78
- italic = false;
79
- underline = false;
80
- }
81
- else if (p === 1) {
82
- bold = true;
83
- }
84
- else if (p === 22) {
85
- bold = false;
86
- }
87
- else if (p === 3) {
88
- italic = true;
89
- }
90
- else if (p === 23) {
91
- italic = false;
92
- }
93
- else if (p === 4) {
94
- underline = true;
95
- }
96
- else if (p === 24) {
97
- underline = false;
98
- }
99
- else if (p >= 30 && p <= 37) {
100
- fg = ANSI16[p - 30];
101
- }
102
- else if (p === 38) {
103
- if (params[i + 1] === 2 && i + 4 < params.length) {
104
- fg = `rgb(${params[i + 2]},${params[i + 3]},${params[i + 4]})`;
105
- i += 4;
106
- }
107
- else if (params[i + 1] === 5 && i + 2 < params.length) {
108
- fg = color256(params[i + 2]);
109
- i += 2;
110
- }
111
- }
112
- else if (p === 39) {
113
- fg = null;
114
- }
115
- else if (p >= 40 && p <= 47) {
116
- bg = ANSI16[p - 40];
117
- }
118
- else if (p === 48) {
119
- if (params[i + 1] === 2 && i + 4 < params.length) {
120
- bg = `rgb(${params[i + 2]},${params[i + 3]},${params[i + 4]})`;
121
- i += 4;
122
- }
123
- else if (params[i + 1] === 5 && i + 2 < params.length) {
124
- bg = color256(params[i + 2]);
125
- i += 2;
126
- }
127
- }
128
- else if (p === 49) {
129
- bg = null;
130
- }
131
- else if (p >= 90 && p <= 97) {
132
- fg = ANSI16[p - 82];
133
- }
134
- else if (p >= 100 && p <= 107) {
135
- bg = ANSI16[p - 92];
136
- }
137
- i++;
138
- }
139
- bufStyle = buildStyle();
140
- }
141
- let i = 0;
142
- while (i < input.length) {
143
- const ch = input[i];
144
- if (ch === "\x1b" && i + 1 < input.length && input[i + 1] === "[") {
145
- // CSI sequence: find the final byte (0x40–0x7e)
146
- const seqStart = i + 2;
147
- let end = seqStart;
148
- while (end < input.length && (input.charCodeAt(end) < 0x40 || input.charCodeAt(end) > 0x7e)) {
149
- end++;
150
- }
151
- if (end < input.length) {
152
- if (input[end] === "m") {
153
- const seq = input.slice(seqStart, end);
154
- const params = seq ? seq.split(";").map((s) => parseInt(s, 10) || 0) : [0];
155
- applySgr(params);
156
- }
157
- // Non-SGR CSI (cursor movement etc.) — silently skip
158
- i = end + 1;
159
- }
160
- else {
161
- // Incomplete sequence at end of input — skip escape char
162
- i++;
163
- }
164
- }
165
- else if (ch === "\r") {
166
- i++;
167
- }
168
- else {
169
- // Regular character — append, flushing if style changed
170
- const style = buildStyle();
171
- if (style !== bufStyle) {
172
- flush();
173
- bufStyle = style;
174
- }
175
- buf += ch;
176
- i++;
177
- }
178
- }
179
- flush();
180
- return result.join("");
181
- }