Skip to content

Commit 6763d59

Browse files
authored
integrate ghostty (#2)
* feat: Ghostty WASM integration POC - TypeScript wrapper for libghostty-vt WASM API (618 lines) - SgrParser: Parse ANSI colors and styles - KeyEncoder: Encode keyboard events to escape sequences - Automatic memory management - Interactive demo (examples/sgr-demo.html) - Shows SGR parser in action - Real-time color parsing with Ghostty's VT parser - Documentation - AGENTS.md: Implementation guide for building terminal - README.md: User-facing overview Key achievement: Actually uses Ghostty's production parser (not custom implementation) Built WASM from Ghostty with Zig 0.15.2 WASM binary (122KB) not committed - build instructions in AGENTS.md * feat: add run-demo.sh script - Automatically builds ghostty-vt.wasm if missing - Checks for Zig and Ghostty source - Starts HTTP server on port 8000 - Shows URL to open in browser
1 parent 22bff04 commit 6763d59

13 files changed

Lines changed: 1493 additions & 1203 deletions

File tree

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
11
zig/.zig-cache
22
zig/zig-out
3+
*.wasm
4+
node_modules/
5+
dist/
6+
.DS_Store

AGENTS.md

Lines changed: 325 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,325 @@
1+
# Agent Guide - Ghostty WASM Terminal
2+
3+
## Quick Start for Agents
4+
5+
This repository integrates **libghostty-vt** (Ghostty's VT100 parser) with WebAssembly to build a terminal emulator.
6+
7+
### What's Implemented
8+
9+
**TypeScript Wrapper (618 lines)**
10+
- `lib/types.ts` - Type definitions for libghostty-vt C API
11+
- `lib/ghostty.ts` - `Ghostty`, `SgrParser`, `KeyEncoder` classes
12+
- Automatic memory management for WASM pointers
13+
14+
**Demo**
15+
- `examples/sgr-demo.html` - Interactive SGR parser demo
16+
17+
### What's Missing (Your Job)
18+
19+
**Terminal Implementation** - The screen buffer, rendering, and state machine:
20+
1. Screen buffer (2D array of cells)
21+
2. Canvas renderer (draw cells with colors)
22+
3. VT100 state machine (parse escape sequences, use Ghostty parsers)
23+
4. Keyboard input handler (use KeyEncoder)
24+
5. PTY connection (IPC to backend)
25+
6. Scrollback buffer
26+
7. Selection/clipboard
27+
28+
## Building the WASM
29+
30+
The WASM binary is **not committed**. Build it:
31+
32+
```bash
33+
# Install Zig 0.15.2 (if not already installed)
34+
cd /tmp
35+
curl -L -o zig-0.15.2.tar.xz \
36+
https://ziglang.org/download/0.15.2/zig-x86_64-linux-0.15.2.tar.xz
37+
tar xf zig-0.15.2.tar.xz
38+
sudo cp -r zig-x86_64-linux-0.15.2 /usr/local/zig-0.15.2
39+
sudo ln -sf /usr/local/zig-0.15.2/zig /usr/local/bin/zig
40+
41+
# Clone Ghostty (if not already)
42+
cd /tmp
43+
git clone https://github.com/ghostty-org/ghostty.git
44+
45+
# Build WASM
46+
cd /tmp/ghostty
47+
zig build lib-vt -Dtarget=wasm32-freestanding -Doptimize=ReleaseSmall
48+
49+
# Copy to project
50+
cp zig-out/bin/ghostty-vt.wasm /path/to/this/repo/
51+
```
52+
53+
**Expected**: `ghostty-vt.wasm` (~122 KB)
54+
55+
## Running the Demo
56+
57+
```bash
58+
cd /path/to/this/repo
59+
python3 -m http.server 8000
60+
# Open: http://localhost:8000/examples/sgr-demo.html
61+
```
62+
63+
## Architecture
64+
65+
```
66+
┌──────────────────────────────────────────┐
67+
│ Terminal (TypeScript) - TODO │
68+
│ - Screen buffer, rendering, events │
69+
└──────────────┬───────────────────────────┘
70+
71+
72+
┌──────────────────────────────────────────┐
73+
│ Ghostty Wrapper (lib/ghostty.ts) ✅ │
74+
│ - SgrParser, KeyEncoder │
75+
└──────────────┬───────────────────────────┘
76+
77+
78+
┌──────────────────────────────────────────┐
79+
│ libghostty-vt.wasm ✅ │
80+
│ - Production VT100 parser │
81+
└──────────────────────────────────────────┘
82+
```
83+
84+
## Using the Ghostty API
85+
86+
### Parse SGR (Colors/Styles)
87+
88+
```typescript
89+
import { Ghostty, SgrAttributeTag } from './lib/ghostty.ts';
90+
91+
const ghostty = await Ghostty.load('./ghostty-vt.wasm');
92+
const parser = ghostty.createSgrParser();
93+
94+
// Parse "bold red" (ESC[1;31m)
95+
for (const attr of parser.parse([1, 31])) {
96+
if (attr.tag === SgrAttributeTag.BOLD) {
97+
cell.bold = true;
98+
}
99+
if (attr.tag === SgrAttributeTag.FG_8) {
100+
cell.fg = attr.color; // 1 = red
101+
}
102+
}
103+
```
104+
105+
### Encode Keys
106+
107+
```typescript
108+
const encoder = ghostty.createKeyEncoder();
109+
encoder.setKittyFlags(KittyKeyFlags.ALL);
110+
111+
const bytes = encoder.encode({
112+
action: KeyAction.PRESS,
113+
key: Key.A,
114+
mods: Mods.CTRL,
115+
});
116+
// Returns: Uint8Array([0x01]) - send to PTY
117+
```
118+
119+
## Implementation Guide
120+
121+
### 1. Create Terminal Class
122+
123+
```typescript
124+
// lib/terminal.ts
125+
export class Terminal {
126+
private buffer: Cell[][];
127+
private cursor: { x: number; y: number };
128+
private ghostty: Ghostty;
129+
private sgrParser: SgrParser;
130+
131+
constructor(cols: number, rows: number) {
132+
// Initialize buffer
133+
this.buffer = Array(rows).fill(null).map(() =>
134+
Array(cols).fill(null).map(() => ({
135+
char: ' ',
136+
fg: 7,
137+
bg: 0,
138+
bold: false,
139+
italic: false,
140+
underline: false,
141+
}))
142+
);
143+
this.cursor = { x: 0, y: 0 };
144+
}
145+
146+
async init() {
147+
this.ghostty = await Ghostty.load('./ghostty-vt.wasm');
148+
this.sgrParser = this.ghostty.createSgrParser();
149+
}
150+
151+
write(data: string) {
152+
// Parse escape sequences
153+
// Use sgrParser when you encounter ESC[...m
154+
// Write characters to buffer
155+
}
156+
157+
render(canvas: HTMLCanvasElement) {
158+
// Draw buffer to canvas
159+
}
160+
}
161+
```
162+
163+
### 2. Parse Escape Sequences
164+
165+
```typescript
166+
// Pseudo-code for VT100 state machine
167+
write(data: string) {
168+
for (const char of data) {
169+
switch (this.state) {
170+
case 'normal':
171+
if (char === '\x1b') {
172+
this.state = 'escape';
173+
} else {
174+
this.writeChar(char);
175+
}
176+
break;
177+
178+
case 'escape':
179+
if (char === '[') {
180+
this.state = 'csi';
181+
this.params = [];
182+
}
183+
break;
184+
185+
case 'csi':
186+
if (char >= '0' && char <= '9') {
187+
// Accumulate parameters
188+
} else if (char === 'm') {
189+
// SGR - use Ghostty parser!
190+
for (const attr of this.sgrParser.parse(this.params)) {
191+
this.applyAttribute(attr);
192+
}
193+
this.state = 'normal';
194+
}
195+
break;
196+
}
197+
}
198+
}
199+
```
200+
201+
### 3. Canvas Rendering
202+
203+
```typescript
204+
render(canvas: HTMLCanvasElement) {
205+
const ctx = canvas.getContext('2d');
206+
const charWidth = 9;
207+
const charHeight = 16;
208+
209+
for (let y = 0; y < this.rows; y++) {
210+
for (let x = 0; x < this.cols; x++) {
211+
const cell = this.buffer[y][x];
212+
213+
// Draw background
214+
ctx.fillStyle = this.getColor(cell.bg);
215+
ctx.fillRect(x * charWidth, y * charHeight, charWidth, charHeight);
216+
217+
// Draw character
218+
ctx.fillStyle = this.getColor(cell.fg);
219+
if (cell.bold) ctx.font = 'bold 14px monospace';
220+
ctx.fillText(cell.char, x * charWidth, y * charHeight + 12);
221+
}
222+
}
223+
}
224+
```
225+
226+
## Testing
227+
228+
### Test SGR Parsing
229+
230+
```bash
231+
# In browser console:
232+
const ghostty = await Ghostty.load('./ghostty-vt.wasm');
233+
const parser = ghostty.createSgrParser();
234+
235+
// Test bold red
236+
for (const attr of parser.parse([1, 31])) {
237+
console.log(attr); // { tag: 2 } (BOLD), { tag: 18, color: 1 } (FG_8)
238+
}
239+
240+
// Test RGB
241+
for (const attr of parser.parse([38, 2, 255, 100, 50])) {
242+
console.log(attr); // { tag: 21, color: { r: 255, g: 100, b: 50 } }
243+
}
244+
```
245+
246+
### Test Key Encoding
247+
248+
```typescript
249+
const encoder = ghostty.createKeyEncoder();
250+
encoder.setKittyFlags(KittyKeyFlags.ALL);
251+
252+
// Test Ctrl+A
253+
const bytes = encoder.encode({
254+
action: KeyAction.PRESS,
255+
key: Key.A,
256+
mods: Mods.CTRL,
257+
});
258+
console.log(bytes); // Uint8Array([1])
259+
```
260+
261+
## File Structure
262+
263+
```
264+
.
265+
├── AGENTS.md # This file
266+
├── README.md # User-facing documentation
267+
├── lib/
268+
│ ├── types.ts # Type definitions
269+
│ ├── ghostty.ts # WASM wrapper
270+
│ └── terminal.ts # TODO: Terminal implementation
271+
├── examples/
272+
│ └── sgr-demo.html # SGR parser demo
273+
└── ghostty-vt.wasm # Built from Ghostty (not committed)
274+
```
275+
276+
## Resources
277+
278+
- [Ghostty Repository](https://github.com/ghostty-org/ghostty)
279+
- [libghostty-vt C API Headers](https://github.com/ghostty-org/ghostty/tree/main/include/ghostty/vt)
280+
- [VT100 User Guide](https://vt100.net/docs/vt100-ug/)
281+
- [ANSI Escape Codes](https://en.wikipedia.org/wiki/ANSI_escape_code)
282+
283+
## Key Decisions
284+
285+
**Why TypeScript + WASM?**
286+
- TypeScript: UI, screen buffer, rendering (easy)
287+
- WASM: VT100 parsing (hard, use Ghostty's proven implementation)
288+
289+
**Why Not Full Ghostty Terminal?**
290+
- Ghostty's Terminal/Screen classes aren't exported to WASM
291+
- Only parsers (SGR, key encoder, OSC) are exported
292+
- This is intentional - the full terminal is complex and Zig-specific
293+
294+
**What to Build in TypeScript vs WASM?**
295+
- TypeScript: Screen buffer, rendering, events, application logic
296+
- WASM: Parsing (SGR colors, key encoding, OSC sequences)
297+
298+
## Next Steps
299+
300+
1. Create `lib/terminal.ts` with Terminal class
301+
2. Implement screen buffer and cursor tracking
302+
3. Add VT100 state machine
303+
4. Implement canvas rendering
304+
5. Add keyboard input handler
305+
6. Connect to PTY backend
306+
7. Add scrollback, selection, clipboard
307+
308+
**Estimated time**: 2-4 weeks for MVP terminal
309+
310+
## Troubleshooting
311+
312+
**WASM not loading?**
313+
- Check file exists: `ls -lh ghostty-vt.wasm`
314+
- Check browser console for fetch errors
315+
- Make sure serving via HTTP (not file://)
316+
317+
**Build errors?**
318+
- Verify Zig version: `zig version` (must be 0.15.2+)
319+
- Update Ghostty: `cd /tmp/ghostty && git pull`
320+
- Clean build: `rm -rf zig-out && zig build lib-vt ...`
321+
322+
**Parser not working?**
323+
- Check WASM exports: `wasm-objdump -x ghostty-vt.wasm | grep export`
324+
- Check browser console for errors
325+
- Test with demo: `http://localhost:8000/examples/sgr-demo.html`

0 commit comments

Comments
 (0)