@aurigax-ai/ghostty-web 0.1.0 → 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 +5 -1
- package/lib/box-glyphs.ts +278 -0
- package/lib/ghostty.ts +244 -126
- package/lib/index.ts +2 -1
- package/lib/input-handler.ts +13 -1
- package/lib/interfaces.ts +6 -0
- package/lib/renderer.ts +550 -688
- package/lib/selection-manager.ts +8 -3
- package/lib/terminal.ts +58 -30
- package/lib/webgl-renderer.ts +870 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# ghostty-web (AurigaX)
|
|
2
2
|
|
|
3
3
|
[Ghostty](https://github.com/ghostty-org/ghostty)'s terminal engine in the browser, with an
|
|
4
|
-
[xterm.js](https://github.com/xtermjs/xterm.js)-shaped API and
|
|
4
|
+
[xterm.js](https://github.com/xtermjs/xterm.js)-shaped API and WebGL and canvas renderers. It is the engine
|
|
5
5
|
behind the Ghostty terminal in Ostia.
|
|
6
6
|
|
|
7
7
|
This is a hard fork of [coder/ghostty-web](https://github.com/coder/ghostty-web) (MIT, © Coder),
|
|
@@ -15,6 +15,10 @@ kept with its history. It does not track upstream. What changed:
|
|
|
15
15
|
(`Abi.addCallback`), so the host can receive prompt marks (OSC 133), the working directory with
|
|
16
16
|
its host (OSC 7), notifications, clipboard writes and unknown sequences, and keep markers as
|
|
17
17
|
tracked grid references.
|
|
18
|
+
- **WebGL renderer.** `renderer: 'webgl'` (the default) draws with WebGL 2 from a glyph atlas,
|
|
19
|
+
rewriting only the rows that changed; without WebGL 2, or when its context is lost, the terminal
|
|
20
|
+
draws with the canvas renderer instead (`renderer: 'canvas'` asks for it). Both draw box drawing
|
|
21
|
+
and block elements from geometry, so they fill their cells.
|
|
18
22
|
- **pnpm and Vitest** instead of Bun; no demo server, benchmark or release automation.
|
|
19
23
|
|
|
20
24
|
## Install
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Box drawing (U+2500–U+257F) and block elements (U+2580–U+259F) drawn from
|
|
3
|
+
* geometry instead of the font, so lines join and blocks fill their cell with
|
|
4
|
+
* no gaps between rows or columns, whatever the font and line height.
|
|
5
|
+
* Characters not covered here (dashes, most doubles, diagonals) fall back to
|
|
6
|
+
* the font.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
type Ctx2D = CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;
|
|
10
|
+
|
|
11
|
+
const NONE = 0;
|
|
12
|
+
const HEAVY = 2;
|
|
13
|
+
const DOUBLE = 3;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Arm weights per character as four digits: up, right, down, left.
|
|
17
|
+
* 0 none, 1 light, 2 heavy, 3 double.
|
|
18
|
+
*/
|
|
19
|
+
const LINES = new Map<number, string>([
|
|
20
|
+
[0x2500, '0101'],
|
|
21
|
+
[0x2501, '0202'],
|
|
22
|
+
[0x2502, '1010'],
|
|
23
|
+
[0x2503, '2020'],
|
|
24
|
+
[0x250c, '0110'],
|
|
25
|
+
[0x250d, '0210'],
|
|
26
|
+
[0x250e, '0120'],
|
|
27
|
+
[0x250f, '0220'],
|
|
28
|
+
[0x2510, '0011'],
|
|
29
|
+
[0x2511, '0012'],
|
|
30
|
+
[0x2512, '0021'],
|
|
31
|
+
[0x2513, '0022'],
|
|
32
|
+
[0x2514, '1100'],
|
|
33
|
+
[0x2515, '1200'],
|
|
34
|
+
[0x2516, '2100'],
|
|
35
|
+
[0x2517, '2200'],
|
|
36
|
+
[0x2518, '1001'],
|
|
37
|
+
[0x2519, '1002'],
|
|
38
|
+
[0x251a, '2001'],
|
|
39
|
+
[0x251b, '2002'],
|
|
40
|
+
[0x251c, '1110'],
|
|
41
|
+
[0x251d, '1210'],
|
|
42
|
+
[0x251e, '2110'],
|
|
43
|
+
[0x251f, '1120'],
|
|
44
|
+
[0x2520, '2120'],
|
|
45
|
+
[0x2521, '2210'],
|
|
46
|
+
[0x2522, '1220'],
|
|
47
|
+
[0x2523, '2220'],
|
|
48
|
+
[0x2524, '1011'],
|
|
49
|
+
[0x2525, '1012'],
|
|
50
|
+
[0x2526, '2011'],
|
|
51
|
+
[0x2527, '1021'],
|
|
52
|
+
[0x2528, '2021'],
|
|
53
|
+
[0x2529, '2012'],
|
|
54
|
+
[0x252a, '1022'],
|
|
55
|
+
[0x252b, '2022'],
|
|
56
|
+
[0x252c, '0111'],
|
|
57
|
+
[0x252d, '0112'],
|
|
58
|
+
[0x252e, '0211'],
|
|
59
|
+
[0x252f, '0212'],
|
|
60
|
+
[0x2530, '0121'],
|
|
61
|
+
[0x2531, '0122'],
|
|
62
|
+
[0x2532, '0221'],
|
|
63
|
+
[0x2533, '0222'],
|
|
64
|
+
[0x2534, '1101'],
|
|
65
|
+
[0x2535, '1102'],
|
|
66
|
+
[0x2536, '1201'],
|
|
67
|
+
[0x2537, '1202'],
|
|
68
|
+
[0x2538, '2101'],
|
|
69
|
+
[0x2539, '2102'],
|
|
70
|
+
[0x253a, '2201'],
|
|
71
|
+
[0x253b, '2202'],
|
|
72
|
+
[0x253c, '1111'],
|
|
73
|
+
[0x253d, '1112'],
|
|
74
|
+
[0x253e, '1211'],
|
|
75
|
+
[0x253f, '1212'],
|
|
76
|
+
[0x2540, '2111'],
|
|
77
|
+
[0x2541, '1121'],
|
|
78
|
+
[0x2542, '2121'],
|
|
79
|
+
[0x2543, '2112'],
|
|
80
|
+
[0x2544, '2211'],
|
|
81
|
+
[0x2545, '1122'],
|
|
82
|
+
[0x2546, '1221'],
|
|
83
|
+
[0x2547, '2212'],
|
|
84
|
+
[0x2548, '1222'],
|
|
85
|
+
[0x2549, '2122'],
|
|
86
|
+
[0x254a, '2221'],
|
|
87
|
+
[0x254b, '2222'],
|
|
88
|
+
[0x2550, '0303'],
|
|
89
|
+
[0x2551, '3030'],
|
|
90
|
+
[0x2574, '0001'],
|
|
91
|
+
[0x2575, '1000'],
|
|
92
|
+
[0x2576, '0100'],
|
|
93
|
+
[0x2577, '0010'],
|
|
94
|
+
[0x2578, '0002'],
|
|
95
|
+
[0x2579, '2000'],
|
|
96
|
+
[0x257a, '0200'],
|
|
97
|
+
[0x257b, '0020'],
|
|
98
|
+
[0x257c, '0201'],
|
|
99
|
+
[0x257d, '1020'],
|
|
100
|
+
[0x257e, '0102'],
|
|
101
|
+
[0x257f, '2010'],
|
|
102
|
+
]);
|
|
103
|
+
|
|
104
|
+
const ARCS = new Map<number, [number, number]>([
|
|
105
|
+
[0x256d, [1, 1]],
|
|
106
|
+
[0x256e, [-1, 1]],
|
|
107
|
+
[0x256f, [-1, -1]],
|
|
108
|
+
[0x2570, [1, -1]],
|
|
109
|
+
]);
|
|
110
|
+
|
|
111
|
+
/** Whether `codepoint` is drawn by `drawBoxGlyph` rather than the font. */
|
|
112
|
+
export function isBoxGlyph(codepoint: number): boolean {
|
|
113
|
+
if (codepoint >= 0x2580 && codepoint <= 0x259f) return true;
|
|
114
|
+
return LINES.has(codepoint) || ARCS.has(codepoint);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Draws `codepoint` into the cell at (x, y) of size w × h with the context's
|
|
119
|
+
* current fill style. Returns false when the character is not covered.
|
|
120
|
+
*/
|
|
121
|
+
export function drawBoxGlyph(
|
|
122
|
+
ctx: Ctx2D,
|
|
123
|
+
codepoint: number,
|
|
124
|
+
x: number,
|
|
125
|
+
y: number,
|
|
126
|
+
w: number,
|
|
127
|
+
h: number
|
|
128
|
+
): boolean {
|
|
129
|
+
if (codepoint >= 0x2580 && codepoint <= 0x259f) {
|
|
130
|
+
drawBlock(ctx, codepoint, x, y, w, h);
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
const light = Math.max(1, Math.round(Math.min(w, h) / 9));
|
|
134
|
+
const arms = LINES.get(codepoint);
|
|
135
|
+
if (arms !== undefined) {
|
|
136
|
+
drawLines(ctx, arms, x, y, w, h, light);
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
const arc = ARCS.get(codepoint);
|
|
140
|
+
if (arc !== undefined) {
|
|
141
|
+
drawArc(ctx, arc, x, y, w, h, light);
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function drawLines(
|
|
148
|
+
ctx: Ctx2D,
|
|
149
|
+
arms: string,
|
|
150
|
+
x: number,
|
|
151
|
+
y: number,
|
|
152
|
+
w: number,
|
|
153
|
+
h: number,
|
|
154
|
+
light: number
|
|
155
|
+
): void {
|
|
156
|
+
const up = arms.charCodeAt(0) - 48;
|
|
157
|
+
const right = arms.charCodeAt(1) - 48;
|
|
158
|
+
const down = arms.charCodeAt(2) - 48;
|
|
159
|
+
const left = arms.charCodeAt(3) - 48;
|
|
160
|
+
const heavy = light * 2;
|
|
161
|
+
const thick = (weight: number) => (weight === HEAVY ? heavy : light);
|
|
162
|
+
const vertical = Math.max(up === NONE ? 0 : thick(up), down === NONE ? 0 : thick(down));
|
|
163
|
+
const horizontal = Math.max(left === NONE ? 0 : thick(left), right === NONE ? 0 : thick(right));
|
|
164
|
+
const cx = x + Math.floor((w - light) / 2);
|
|
165
|
+
const cy = y + Math.floor((h - light) / 2);
|
|
166
|
+
const centerX = (t: number) => cx - Math.floor((t - light) / 2);
|
|
167
|
+
const centerY = (t: number) => cy - Math.floor((t - light) / 2);
|
|
168
|
+
|
|
169
|
+
const armV = (weight: number, fromY: number, toY: number) => {
|
|
170
|
+
if (weight === NONE || toY <= fromY) return;
|
|
171
|
+
if (weight === DOUBLE) {
|
|
172
|
+
ctx.fillRect(cx - light, fromY, light, toY - fromY);
|
|
173
|
+
ctx.fillRect(cx + light, fromY, light, toY - fromY);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const t = thick(weight);
|
|
177
|
+
ctx.fillRect(centerX(t), fromY, t, toY - fromY);
|
|
178
|
+
};
|
|
179
|
+
const armH = (weight: number, fromX: number, toX: number) => {
|
|
180
|
+
if (weight === NONE || toX <= fromX) return;
|
|
181
|
+
if (weight === DOUBLE) {
|
|
182
|
+
ctx.fillRect(fromX, cy - light, toX - fromX, light);
|
|
183
|
+
ctx.fillRect(fromX, cy + light, toX - fromX, light);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const t = thick(weight);
|
|
187
|
+
ctx.fillRect(fromX, centerY(t), toX - fromX, t);
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const joinTop = horizontal > 0 ? centerY(horizontal) : cy;
|
|
191
|
+
const joinBottom = horizontal > 0 ? centerY(horizontal) + horizontal : cy;
|
|
192
|
+
const joinLeft = vertical > 0 ? centerX(vertical) : cx;
|
|
193
|
+
const joinRight = vertical > 0 ? centerX(vertical) + vertical : cx;
|
|
194
|
+
armV(up, y, up === DOUBLE ? cy + light : joinBottom);
|
|
195
|
+
armV(down, down === DOUBLE ? cy : joinTop, y + h);
|
|
196
|
+
armH(left, x, left === DOUBLE ? cx + light : joinRight);
|
|
197
|
+
armH(right, right === DOUBLE ? cx : joinLeft, x + w);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function drawArc(
|
|
201
|
+
ctx: Ctx2D,
|
|
202
|
+
[dx, dy]: [number, number],
|
|
203
|
+
x: number,
|
|
204
|
+
y: number,
|
|
205
|
+
w: number,
|
|
206
|
+
h: number,
|
|
207
|
+
light: number
|
|
208
|
+
): void {
|
|
209
|
+
const cx = x + Math.floor((w - light) / 2) + light / 2;
|
|
210
|
+
const cy = y + Math.floor((h - light) / 2) + light / 2;
|
|
211
|
+
const r = Math.min(w, h) / 2;
|
|
212
|
+
const edgeX = dx > 0 ? x + w : x;
|
|
213
|
+
const edgeY = dy > 0 ? y + h : y;
|
|
214
|
+
const style = ctx.fillStyle;
|
|
215
|
+
ctx.save();
|
|
216
|
+
ctx.beginPath();
|
|
217
|
+
ctx.rect(x, y, w, h);
|
|
218
|
+
ctx.clip();
|
|
219
|
+
ctx.strokeStyle = style;
|
|
220
|
+
ctx.lineWidth = light;
|
|
221
|
+
ctx.lineCap = 'butt';
|
|
222
|
+
ctx.beginPath();
|
|
223
|
+
ctx.moveTo(cx, edgeY);
|
|
224
|
+
ctx.lineTo(cx, cy + dy * r);
|
|
225
|
+
ctx.arcTo(cx, cy, cx + dx * r, cy, r);
|
|
226
|
+
ctx.lineTo(edgeX, cy);
|
|
227
|
+
ctx.stroke();
|
|
228
|
+
ctx.restore();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function drawBlock(ctx: Ctx2D, cp: number, x: number, y: number, w: number, h: number): void {
|
|
232
|
+
const rect = (fx0: number, fy0: number, fx1: number, fy1: number) => {
|
|
233
|
+
const x0 = x + Math.round(w * fx0);
|
|
234
|
+
const x1 = x + Math.round(w * fx1);
|
|
235
|
+
const y0 = y + Math.round(h * fy0);
|
|
236
|
+
const y1 = y + Math.round(h * fy1);
|
|
237
|
+
ctx.fillRect(x0, y0, x1 - x0, y1 - y0);
|
|
238
|
+
};
|
|
239
|
+
if (cp === 0x2580) {
|
|
240
|
+
rect(0, 0, 1, 0.5);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
if (cp >= 0x2581 && cp <= 0x2588) {
|
|
244
|
+
rect(0, 1 - (cp - 0x2580) / 8, 1, 1);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
if (cp >= 0x2589 && cp <= 0x258f) {
|
|
248
|
+
rect(0, 0, (0x2590 - cp) / 8, 1);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (cp === 0x2590) {
|
|
252
|
+
rect(0.5, 0, 1, 1);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
if (cp >= 0x2591 && cp <= 0x2593) {
|
|
256
|
+
const alpha = ctx.globalAlpha;
|
|
257
|
+
ctx.globalAlpha = alpha * ((cp - 0x2590) / 4);
|
|
258
|
+
rect(0, 0, 1, 1);
|
|
259
|
+
ctx.globalAlpha = alpha;
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (cp === 0x2594) {
|
|
263
|
+
rect(0, 0, 1, 1 / 8);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
if (cp === 0x2595) {
|
|
267
|
+
rect(7 / 8, 0, 1, 1);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
const quadrants = QUADRANTS[cp - 0x2596];
|
|
271
|
+
if (quadrants & 1) rect(0, 0, 0.5, 0.5);
|
|
272
|
+
if (quadrants & 2) rect(0.5, 0, 1, 0.5);
|
|
273
|
+
if (quadrants & 4) rect(0, 0.5, 0.5, 1);
|
|
274
|
+
if (quadrants & 8) rect(0.5, 0.5, 1, 1);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** U+2596–U+259F as bits: 1 upper left, 2 upper right, 4 lower left, 8 lower right. */
|
|
278
|
+
const QUADRANTS = [4, 8, 1, 1 | 4 | 8, 1 | 8, 1 | 2 | 4, 1 | 2 | 8, 2, 2 | 4, 2 | 4 | 8];
|