File size: 2,627 Bytes
aab0173
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import "@xterm/xterm/css/xterm.css";

const THEME = {
  background: "#10141c",
  foreground: "#d6deeb",
  cursor: "#f7c948",
  cursorAccent: "#10141c",
  selectionBackground: "#334155",
  black: "#111827",
  red: "#f87171",
  green: "#34d399",
  yellow: "#facc15",
  blue: "#60a5fa",
  magenta: "#c084fc",
  cyan: "#22d3ee",
  white: "#f8fafc",
  brightBlack: "#475569",
  brightRed: "#fca5a5",
  brightGreen: "#86efac",
  brightYellow: "#fde68a",
  brightBlue: "#93c5fd",
  brightMagenta: "#d8b4fe",
  brightCyan: "#67e8f9",
  brightWhite: "#ffffff",
};

function normalizeNewlines(text) {
  return String(text).replace(/\r?\n/g, "\r\n");
}

function withTimeout(promise, ms, label) {
  return Promise.race([
    promise,
    new Promise((_, reject) => {
      setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
    }),
  ]);
}

export async function createWebTerminal(container) {
  let terminal;
  let fitAddon;
  let engine = "ghostty-web";

  try {
    const ghostty = await withTimeout(import("ghostty-web"), 3000, "ghostty-web import");
    await withTimeout(ghostty.init(), 3000, "ghostty-web init");
    terminal = new ghostty.Terminal({
      cursorBlink: true,
      fontFamily: "JetBrains Mono, SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace",
      fontSize: 14,
      rows: 30,
      cols: 100,
      scrollback: 5000,
      theme: THEME,
    });
    fitAddon = new ghostty.FitAddon();
    terminal.loadAddon(fitAddon);
  } catch (error) {
    const [{ Terminal }, { FitAddon }] = await Promise.all([import("@xterm/xterm"), import("@xterm/addon-fit")]);
    engine = "xterm";
    terminal = new Terminal({
      cursorBlink: true,
      fontFamily: "JetBrains Mono, SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace",
      fontSize: 14,
      rows: 30,
      cols: 100,
      scrollback: 5000,
      theme: THEME,
    });
    fitAddon = new FitAddon();
    terminal.loadAddon(fitAddon);
    console.warn("ghostty-web failed, using xterm fallback", error);
  }

  terminal.open(container);
  fitAddon.fit();
  terminal.focus();

  const resizeObserver = new ResizeObserver(() => fitAddon.fit());
  resizeObserver.observe(container);

  return {
    engine,
    raw: terminal,
    onData: (handler) => terminal.onData(handler),
    write: (text) => terminal.write(normalizeNewlines(text)),
    writeln: (text = "") => terminal.write(`${normalizeNewlines(text)}\r\n`),
    clear: () => terminal.clear(),
    focus: () => terminal.focus(),
    fit: () => fitAddon.fit(),
    dispose: () => {
      resizeObserver.disconnect();
      terminal.dispose();
    },
  };
}