Skip to content

Commit 90c1178

Browse files
authored
feat: migrate to use RenderState (#75)
1 parent 59bb045 commit 90c1178

File tree

14 files changed

+2259
-1494
lines changed

14 files changed

+2259
-1494
lines changed

bench/versus.ts

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import { Terminal as XTerm } from '@xterm/xterm';
2+
import { bench, group, run } from 'mitata';
3+
import { Ghostty, Terminal as GhosttyTerminal } from '../lib';
4+
import '../happydom';
5+
6+
function generateColorText(lines: number): string {
7+
const colors = [31, 32, 33, 34, 35, 36];
8+
let output = '';
9+
for (let i = 0; i < lines; i++) {
10+
const color = colors[i % colors.length];
11+
output += `\x1b[${color}mLine ${i}: This is some colored text with ANSI escape sequences\x1b[0m\r\n`;
12+
}
13+
return output;
14+
}
15+
16+
function generateComplexVT(lines: number): string {
17+
let output = '';
18+
for (let i = 0; i < lines; i++) {
19+
output += `\x1b[1;4;38;2;255;128;0mBold underline RGB\x1b[0m `;
20+
output += `\x1b[48;5;236mBG 256\x1b[0m `;
21+
output += `\x1b[7mInverse\x1b[0m\r\n`;
22+
}
23+
return output;
24+
}
25+
26+
function generateRawBytes(size: number): Uint8Array {
27+
const data = new Uint8Array(size);
28+
for (let i = 0; i < size; i++) {
29+
const mod = i % 85;
30+
if (mod < 80) {
31+
data[i] = 32 + (i % 95); // Printable ASCII
32+
} else if (mod === 80) {
33+
data[i] = 13; // \r
34+
} else {
35+
data[i] = 10; // \n
36+
}
37+
}
38+
return data;
39+
}
40+
41+
function generateCursorMovement(ops: number): string {
42+
let output = '';
43+
for (let i = 0; i < ops; i++) {
44+
output += `\x1b[${(i % 24) + 1};${(i % 80) + 1}H`; // Cursor position
45+
output += `\x1b[K`; // Clear to end of line
46+
output += `Text at position ${i}`;
47+
output += `\x1b[A\x1b[B\x1b[C\x1b[D`; // Up, Down, Right, Left
48+
}
49+
return output;
50+
}
51+
52+
const withTerminals = async (fn: (term: GhosttyTerminal | XTerm) => Promise<void>) => {
53+
const ghostty = await Ghostty.load();
54+
bench('ghostty-web', async () => {
55+
const container = document.createElement('div');
56+
document.body.appendChild(container);
57+
const term = new GhosttyTerminal({ ghostty });
58+
await term.open(container);
59+
await fn(term);
60+
await term.dispose();
61+
});
62+
bench('xterm.js', async () => {
63+
const xterm = new XTerm();
64+
const container = document.createElement('div');
65+
document.body.appendChild(container);
66+
await xterm.open(container);
67+
await fn(xterm);
68+
await xterm.dispose();
69+
});
70+
};
71+
72+
const throughput = async (prefix: string, data: Record<string, Uint8Array | string>) => {
73+
await Promise.all(
74+
Object.entries(data).map(async ([name, data]) => {
75+
await group(`${prefix}: ${name}`, async () => {
76+
await withTerminals(async (term) => {
77+
await new Promise<void>((resolve) => {
78+
term.write(data, resolve);
79+
});
80+
});
81+
});
82+
})
83+
);
84+
};
85+
86+
await throughput('raw bytes', {
87+
'1KB': generateRawBytes(1024),
88+
'10KB': generateRawBytes(10 * 1024),
89+
'100KB': generateRawBytes(100 * 1024),
90+
'1MB': generateRawBytes(1024 * 1024),
91+
});
92+
93+
await throughput('color text', {
94+
'100 lines': generateColorText(100),
95+
'1000 lines': generateColorText(1000),
96+
'10000 lines': generateColorText(10000),
97+
});
98+
99+
await throughput('complex VT', {
100+
'100 lines': generateComplexVT(100),
101+
'1000 lines': generateComplexVT(1000),
102+
'10000 lines': generateComplexVT(10000),
103+
});
104+
105+
await throughput('cursor movement', {
106+
'1000 operations': generateCursorMovement(1000),
107+
'10000 operations': generateCursorMovement(10000),
108+
'100000 operations': generateCursorMovement(100000),
109+
});
110+
111+
await group('read full viewport', async () => {
112+
await withTerminals(async (term) => {
113+
const lines = term.rows;
114+
for (let i = 0; i < lines; i++) {
115+
const line = term.buffer.active.getLine(i);
116+
if (!line) {
117+
continue;
118+
}
119+
line.translateToString();
120+
}
121+
});
122+
});
123+
124+
await run();

bun.lock

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

demo/bin/demo.js

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -473,23 +473,20 @@ wss.on('connection', (ws, req) => {
473473
});
474474

475475
// Send welcome message
476-
setTimeout(() => {
477-
if (ws.readyState !== ws.OPEN) return;
478-
const C = '\x1b[1;36m'; // Cyan
479-
const G = '\x1b[1;32m'; // Green
480-
const Y = '\x1b[1;33m'; // Yellow
481-
const R = '\x1b[0m'; // Reset
482-
ws.send(`${C}╔══════════════════════════════════════════════════════════════╗${R}\r\n`);
483-
ws.send(
484-
`${C}${R} ${G}Welcome to ghostty-web!${R} ${C}${R}\r\n`
485-
);
486-
ws.send(`${C}${R} ${C}${R}\r\n`);
487-
ws.send(`${C}${R} You have a real shell session with full PTY support. ${C}${R}\r\n`);
488-
ws.send(
489-
`${C}${R} Try: ${Y}ls${R}, ${Y}cd${R}, ${Y}top${R}, ${Y}vim${R}, or any command! ${C}${R}\r\n`
490-
);
491-
ws.send(`${C}╚══════════════════════════════════════════════════════════════╝${R}\r\n\r\n`);
492-
}, 100);
476+
const C = '\x1b[1;36m'; // Cyan
477+
const G = '\x1b[1;32m'; // Green
478+
const Y = '\x1b[1;33m'; // Yellow
479+
const R = '\x1b[0m'; // Reset
480+
ws.send(`${C}╔══════════════════════════════════════════════════════════════╗${R}\r\n`);
481+
ws.send(
482+
`${C}${R} ${G}Welcome to ghostty-web!${R} ${C}${R}\r\n`
483+
);
484+
ws.send(`${C}${R} ${C}${R}\r\n`);
485+
ws.send(`${C}${R} You have a real shell session with full PTY support. ${C}${R}\r\n`);
486+
ws.send(
487+
`${C}${R} Try: ${Y}ls${R}, ${Y}cd${R}, ${Y}top${R}, ${Y}vim${R}, or any command! ${C}${R}\r\n`
488+
);
489+
ws.send(`${C}╚══════════════════════════════════════════════════════════════╝${R}\r\n\r\n`);
493490
});
494491

495492
// ============================================================================

flake.lock

Lines changed: 147 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

flake.nix

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{
2+
description = "ghostty-web - Web terminal using Ghostty's VT100 parser via WASM";
3+
4+
inputs = {
5+
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
6+
flake-utils.url = "github:numtide/flake-utils";
7+
zig-overlay.url = "github:mitchellh/zig-overlay";
8+
};
9+
10+
outputs = { self, nixpkgs, flake-utils, zig-overlay }:
11+
flake-utils.lib.eachDefaultSystem (system:
12+
let
13+
pkgs = import nixpkgs {
14+
inherit system;
15+
overlays = [ zig-overlay.overlays.default ];
16+
};
17+
zig = pkgs.zigpkgs."0.15.2";
18+
in {
19+
devShells.default = pkgs.mkShell {
20+
buildInputs = [
21+
pkgs.bun
22+
pkgs.nodejs_22
23+
zig
24+
];
25+
};
26+
27+
packages.default = pkgs.stdenv.mkDerivation {
28+
pname = "ghostty-web";
29+
version = "0.3.0";
30+
31+
src = ./.;
32+
33+
nativeBuildInputs = [ pkgs.bun pkgs.nodejs_22 ];
34+
35+
buildPhase = ''
36+
export HOME=$TMPDIR
37+
bun install --frozen-lockfile
38+
bun run build
39+
'';
40+
41+
installPhase = ''
42+
mkdir -p $out
43+
cp -r dist/* $out/
44+
'';
45+
};
46+
}
47+
);
48+
}

ghostty

Submodule ghostty updated 294 files

0 commit comments

Comments
 (0)