@crunchloop/ghostty-web 0.4.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.
@@ -0,0 +1,3389 @@
1
+ import { K as c, a as A, M as T, b as Y, C as x, c as D, E as k, T as G, G as q } from "./terminal-core-b557858d.js";
2
+ import { D as wt, d as bt, e as vt } from "./terminal-core-b557858d.js";
3
+ const $ = {
4
+ // Letters
5
+ KeyA: c.A,
6
+ KeyB: c.B,
7
+ KeyC: c.C,
8
+ KeyD: c.D,
9
+ KeyE: c.E,
10
+ KeyF: c.F,
11
+ KeyG: c.G,
12
+ KeyH: c.H,
13
+ KeyI: c.I,
14
+ KeyJ: c.J,
15
+ KeyK: c.K,
16
+ KeyL: c.L,
17
+ KeyM: c.M,
18
+ KeyN: c.N,
19
+ KeyO: c.O,
20
+ KeyP: c.P,
21
+ KeyQ: c.Q,
22
+ KeyR: c.R,
23
+ KeyS: c.S,
24
+ KeyT: c.T,
25
+ KeyU: c.U,
26
+ KeyV: c.V,
27
+ KeyW: c.W,
28
+ KeyX: c.X,
29
+ KeyY: c.Y,
30
+ KeyZ: c.Z,
31
+ // Numbers
32
+ Digit1: c.ONE,
33
+ Digit2: c.TWO,
34
+ Digit3: c.THREE,
35
+ Digit4: c.FOUR,
36
+ Digit5: c.FIVE,
37
+ Digit6: c.SIX,
38
+ Digit7: c.SEVEN,
39
+ Digit8: c.EIGHT,
40
+ Digit9: c.NINE,
41
+ Digit0: c.ZERO,
42
+ // Special keys
43
+ Enter: c.ENTER,
44
+ Escape: c.ESCAPE,
45
+ Backspace: c.BACKSPACE,
46
+ Tab: c.TAB,
47
+ Space: c.SPACE,
48
+ // Punctuation
49
+ Minus: c.MINUS,
50
+ Equal: c.EQUAL,
51
+ BracketLeft: c.BRACKET_LEFT,
52
+ BracketRight: c.BRACKET_RIGHT,
53
+ Backslash: c.BACKSLASH,
54
+ Semicolon: c.SEMICOLON,
55
+ Quote: c.QUOTE,
56
+ Backquote: c.GRAVE,
57
+ Comma: c.COMMA,
58
+ Period: c.PERIOD,
59
+ Slash: c.SLASH,
60
+ // Function keys
61
+ CapsLock: c.CAPS_LOCK,
62
+ F1: c.F1,
63
+ F2: c.F2,
64
+ F3: c.F3,
65
+ F4: c.F4,
66
+ F5: c.F5,
67
+ F6: c.F6,
68
+ F7: c.F7,
69
+ F8: c.F8,
70
+ F9: c.F9,
71
+ F10: c.F10,
72
+ F11: c.F11,
73
+ F12: c.F12,
74
+ // Special function keys
75
+ PrintScreen: c.PRINT_SCREEN,
76
+ ScrollLock: c.SCROLL_LOCK,
77
+ Pause: c.PAUSE,
78
+ Insert: c.INSERT,
79
+ Home: c.HOME,
80
+ PageUp: c.PAGE_UP,
81
+ Delete: c.DELETE,
82
+ End: c.END,
83
+ PageDown: c.PAGE_DOWN,
84
+ // Arrow keys
85
+ ArrowRight: c.RIGHT,
86
+ ArrowLeft: c.LEFT,
87
+ ArrowDown: c.DOWN,
88
+ ArrowUp: c.UP,
89
+ // Keypad
90
+ NumLock: c.NUM_LOCK,
91
+ NumpadDivide: c.KP_DIVIDE,
92
+ NumpadMultiply: c.KP_MULTIPLY,
93
+ NumpadSubtract: c.KP_MINUS,
94
+ NumpadAdd: c.KP_PLUS,
95
+ NumpadEnter: c.KP_ENTER,
96
+ Numpad1: c.KP_1,
97
+ Numpad2: c.KP_2,
98
+ Numpad3: c.KP_3,
99
+ Numpad4: c.KP_4,
100
+ Numpad5: c.KP_5,
101
+ Numpad6: c.KP_6,
102
+ Numpad7: c.KP_7,
103
+ Numpad8: c.KP_8,
104
+ Numpad9: c.KP_9,
105
+ Numpad0: c.KP_0,
106
+ NumpadDecimal: c.KP_PERIOD,
107
+ // International
108
+ IntlBackslash: c.INTL_BACKSLASH,
109
+ ContextMenu: c.CONTEXT_MENU,
110
+ // Additional function keys
111
+ F13: c.F13,
112
+ F14: c.F14,
113
+ F15: c.F15,
114
+ F16: c.F16,
115
+ F17: c.F17,
116
+ F18: c.F18,
117
+ F19: c.F19,
118
+ F20: c.F20,
119
+ F21: c.F21,
120
+ F22: c.F22,
121
+ F23: c.F23,
122
+ F24: c.F24
123
+ }, V = class L {
124
+ /**
125
+ * Create a new InputHandler
126
+ * @param ghostty - Ghostty instance (for creating KeyEncoder)
127
+ * @param container - DOM element to attach listeners to
128
+ * @param onData - Callback for terminal data (escape sequences to send to PTY)
129
+ * @param onBell - Callback for bell/beep event
130
+ * @param onKey - Optional callback for raw key events
131
+ * @param customKeyEventHandler - Optional custom key event handler
132
+ * @param getMode - Optional callback to query terminal mode state (for application cursor mode)
133
+ * @param onCopy - Optional callback to handle copy (Cmd+C/Ctrl+C with selection)
134
+ * @param inputElement - Optional input element for beforeinput events
135
+ * @param mouseConfig - Optional mouse tracking configuration
136
+ */
137
+ constructor(t, i, e, s, r, n, o, a, l, h) {
138
+ this.keydownListener = null, this.keypressListener = null, this.pasteListener = null, this.beforeInputListener = null, this.compositionStartListener = null, this.compositionUpdateListener = null, this.compositionEndListener = null, this.mousedownListener = null, this.mouseupListener = null, this.mousemoveListener = null, this.wheelListener = null, this.isComposing = !1, this.compositionJustEnded = !1, this.pendingKeyAfterComposition = null, this.isDisposed = !1, this.mouseButtonsPressed = 0, this.lastKeyDownData = null, this.lastKeyDownTime = 0, this.lastPasteData = null, this.lastPasteTime = 0, this.lastPasteSource = null, this.lastCompositionData = null, this.lastCompositionTime = 0, this.lastBeforeInputData = null, this.lastBeforeInputTime = 0, this.syncedEncoderOptions = /* @__PURE__ */ new Map(), this.decoder = new TextDecoder(), this.encoder = t.createKeyEncoder(), this.encoder.setOption(A.ALT_ESC_PREFIX, !0), this.container = i, this.inputElement = l, this.onDataCallback = e, this.onBellCallback = s, this.onKeyCallback = r, this.customKeyEventHandler = n, this.getModeCallback = o, this.onCopyCallback = a, this.mouseConfig = h, this.attach();
139
+ }
140
+ /**
141
+ * Set custom key event handler (for runtime updates)
142
+ */
143
+ setCustomKeyEventHandler(t) {
144
+ this.customKeyEventHandler = t;
145
+ }
146
+ /**
147
+ * Attach keyboard event listeners to container
148
+ */
149
+ attach() {
150
+ typeof this.container.hasAttribute == "function" && typeof this.container.setAttribute == "function" && (this.container.hasAttribute("tabindex") || this.container.setAttribute("tabindex", "0"), this.container.style && (this.container.style.outline = "none")), this.keydownListener = this.handleKeyDown.bind(this), this.container.addEventListener("keydown", this.keydownListener), this.pasteListener = this.handlePaste.bind(this), this.container.addEventListener("paste", this.pasteListener), this.inputElement && this.inputElement !== this.container && this.inputElement.addEventListener("paste", this.pasteListener), this.inputElement && (this.beforeInputListener = this.handleBeforeInput.bind(this), this.inputElement.addEventListener("beforeinput", this.beforeInputListener));
151
+ const t = this.inputElement || this.container;
152
+ this.compositionStartListener = this.handleCompositionStart.bind(this), t.addEventListener("compositionstart", this.compositionStartListener), this.compositionUpdateListener = this.handleCompositionUpdate.bind(this), t.addEventListener("compositionupdate", this.compositionUpdateListener), this.compositionEndListener = this.handleCompositionEnd.bind(this), t.addEventListener("compositionend", this.compositionEndListener), this.mousedownListener = this.handleMouseDown.bind(this), this.container.addEventListener("mousedown", this.mousedownListener), this.mouseupListener = this.handleMouseUp.bind(this), this.container.addEventListener("mouseup", this.mouseupListener), this.mousemoveListener = this.handleMouseMove.bind(this), this.container.addEventListener("mousemove", this.mousemoveListener), this.wheelListener = this.handleWheel.bind(this), this.container.addEventListener("wheel", this.wheelListener, { passive: !1 });
153
+ }
154
+ /**
155
+ * Map KeyboardEvent.code to USB HID Key enum value
156
+ * @param code - KeyboardEvent.code value
157
+ * @returns Key enum value or null if unmapped
158
+ */
159
+ mapKeyCode(t) {
160
+ return $[t] ?? null;
161
+ }
162
+ /**
163
+ * Push an encoder option value to WASM only if it differs from the last
164
+ * value we pushed. Terminal modes rarely change between keystrokes, so
165
+ * this saves two WASM round-trips per keystroke in the steady state.
166
+ */
167
+ syncEncoderOption(t, i) {
168
+ this.syncedEncoderOptions.get(t) !== i && (this.encoder.setOption(t, i), this.syncedEncoderOptions.set(t, i));
169
+ }
170
+ /**
171
+ * Extract modifier flags from KeyboardEvent
172
+ * @param event - KeyboardEvent
173
+ * @returns Mods flags
174
+ */
175
+ extractModifiers(t) {
176
+ let i = T.NONE;
177
+ return t.shiftKey && (i |= T.SHIFT), t.ctrlKey && (i |= T.CTRL), t.altKey && (i |= T.ALT), t.metaKey && (i |= T.SUPER), i;
178
+ }
179
+ /**
180
+ * Handle keydown event
181
+ * @param event - KeyboardEvent
182
+ */
183
+ handleKeyDown(t) {
184
+ if (this.isDisposed || t.isComposing || t.keyCode === 229)
185
+ return;
186
+ if (this.isComposing) {
187
+ this.pendingKeyAfterComposition = t.key, t.preventDefault();
188
+ return;
189
+ }
190
+ if (this.compositionJustEnded) {
191
+ this.compositionJustEnded = !1;
192
+ return;
193
+ }
194
+ if (this.onKeyCallback && this.onKeyCallback({ key: t.key, domEvent: t }), this.customKeyEventHandler && this.customKeyEventHandler(t)) {
195
+ t.preventDefault();
196
+ return;
197
+ }
198
+ if ((t.ctrlKey || t.metaKey) && t.code === "KeyV") {
199
+ const n = this.encoder.encode({
200
+ key: c.V,
201
+ mods: t.ctrlKey ? T.CTRL : T.SUPER,
202
+ action: Y.PRESS
203
+ });
204
+ n.length > 0 && this.onDataCallback(new TextDecoder().decode(n));
205
+ return;
206
+ }
207
+ if (t.metaKey && t.code === "KeyC") {
208
+ this.onCopyCallback && this.onCopyCallback() && t.preventDefault();
209
+ return;
210
+ }
211
+ const i = this.mapKeyCode(t.code);
212
+ if (i === null)
213
+ return;
214
+ const e = this.extractModifiers(t);
215
+ let s;
216
+ if (t.key.length > 0 && t.key !== "Dead" && t.key !== "Unidentified") {
217
+ const n = t.key.codePointAt(0), o = n !== void 0 && n > 65535 ? 2 : 1;
218
+ t.key.length === o && (t.altKey && n !== void 0 && n > 127 ? t.code.startsWith("Key") && t.code.length === 4 && (s = t.code[3].toLowerCase()) : s = t.key);
219
+ }
220
+ this.getModeCallback && (this.syncEncoderOption(A.CURSOR_KEY_APPLICATION, this.getModeCallback(1)), this.syncEncoderOption(A.KEYPAD_KEY_APPLICATION, this.getModeCallback(66))), t.preventDefault(), t.stopPropagation();
221
+ let r;
222
+ try {
223
+ const n = this.encoder.encode({
224
+ action: Y.PRESS,
225
+ key: i,
226
+ mods: e,
227
+ utf8: s
228
+ });
229
+ r = n.length === 0 ? "" : this.decoder.decode(n);
230
+ } catch (n) {
231
+ console.warn("Failed to encode key:", t.code, n);
232
+ return;
233
+ }
234
+ r.length > 0 && (this.onDataCallback(r), this.recordKeyDownData(r));
235
+ }
236
+ /**
237
+ * Handle paste event from clipboard
238
+ * @param event - ClipboardEvent
239
+ */
240
+ handlePaste(t) {
241
+ if (this.isDisposed)
242
+ return;
243
+ const i = t.clipboardData;
244
+ if (!i)
245
+ return;
246
+ const e = i.getData("text/plain");
247
+ e && (t.preventDefault(), t.stopPropagation(), !this.shouldIgnorePasteEvent(e, "paste") && (this.emitPasteData(e), this.recordPasteData(e, "paste")));
248
+ }
249
+ /**
250
+ * Handle beforeinput event (mobile/IME input)
251
+ * @param event - InputEvent
252
+ */
253
+ handleBeforeInput(t) {
254
+ if (this.isDisposed || this.isComposing || t.isComposing)
255
+ return;
256
+ const i = t.inputType, e = t.data ?? "";
257
+ let s = null;
258
+ switch (i) {
259
+ case "insertText":
260
+ case "insertReplacementText":
261
+ s = e.length > 0 ? e.replace(/\n/g, "\r") : null;
262
+ break;
263
+ case "insertLineBreak":
264
+ case "insertParagraph":
265
+ s = "\r";
266
+ break;
267
+ case "deleteContentBackward":
268
+ s = "";
269
+ break;
270
+ case "deleteContentForward":
271
+ s = "\x1B[3~";
272
+ break;
273
+ case "insertFromPaste":
274
+ if (!e)
275
+ return;
276
+ if (this.shouldIgnorePasteEvent(e, "beforeinput")) {
277
+ t.preventDefault(), t.stopPropagation();
278
+ return;
279
+ }
280
+ t.preventDefault(), t.stopPropagation(), this.emitPasteData(e), this.recordPasteData(e, "beforeinput");
281
+ return;
282
+ default:
283
+ return;
284
+ }
285
+ if (s) {
286
+ if (this.shouldIgnoreBeforeInput(s)) {
287
+ t.preventDefault(), t.stopPropagation();
288
+ return;
289
+ }
290
+ if (e && this.shouldIgnoreBeforeInputFromComposition(e)) {
291
+ t.preventDefault(), t.stopPropagation();
292
+ return;
293
+ }
294
+ t.preventDefault(), t.stopPropagation(), this.onDataCallback(s), e && this.recordBeforeInputData(e);
295
+ }
296
+ }
297
+ /**
298
+ * Handle compositionstart event
299
+ */
300
+ handleCompositionStart(t) {
301
+ this.isDisposed || (this.isComposing = !0);
302
+ }
303
+ /**
304
+ * Handle compositionupdate event
305
+ */
306
+ handleCompositionUpdate(t) {
307
+ this.isDisposed;
308
+ }
309
+ /**
310
+ * Handle compositionend event
311
+ */
312
+ handleCompositionEnd(t) {
313
+ if (this.isDisposed)
314
+ return;
315
+ this.isComposing = !1;
316
+ const i = t.data;
317
+ if (i && i.length > 0) {
318
+ if (this.shouldIgnoreCompositionEnd(i)) {
319
+ this.cleanupCompositionTextNodes(), this.processPendingKeyAfterComposition();
320
+ return;
321
+ }
322
+ this.onDataCallback(i), this.recordCompositionData(i);
323
+ }
324
+ this.cleanupCompositionTextNodes(), this.processPendingKeyAfterComposition();
325
+ }
326
+ /**
327
+ * Process the pending key that was queued during composition
328
+ */
329
+ processPendingKeyAfterComposition() {
330
+ if (this.pendingKeyAfterComposition) {
331
+ const t = this.pendingKeyAfterComposition;
332
+ this.pendingKeyAfterComposition = null, this.onDataCallback(t);
333
+ }
334
+ }
335
+ /**
336
+ * Cleanup text nodes in container after composition
337
+ */
338
+ cleanupCompositionTextNodes() {
339
+ if (this.container && this.container.childNodes)
340
+ for (let t = this.container.childNodes.length - 1; t >= 0; t--) {
341
+ const i = this.container.childNodes[t];
342
+ i.nodeType === 3 && this.container.removeChild(i);
343
+ }
344
+ }
345
+ // ==========================================================================
346
+ // Mouse Event Handling (for terminal mouse tracking)
347
+ // ==========================================================================
348
+ /**
349
+ * Convert pixel coordinates to terminal cell coordinates
350
+ */
351
+ pixelToCell(t) {
352
+ if (!this.mouseConfig)
353
+ return null;
354
+ const i = this.mouseConfig.getCellDimensions(), e = this.mouseConfig.getCanvasOffset();
355
+ if (i.width <= 0 || i.height <= 0)
356
+ return null;
357
+ const s = t.clientX - e.left, r = t.clientY - e.top, n = Math.floor(s / i.width) + 1, o = Math.floor(r / i.height) + 1;
358
+ return {
359
+ col: Math.max(1, n),
360
+ row: Math.max(1, o)
361
+ };
362
+ }
363
+ /**
364
+ * Get modifier flags for mouse event
365
+ */
366
+ getMouseModifiers(t) {
367
+ let i = 0;
368
+ return t.shiftKey && (i |= 4), t.metaKey && (i |= 8), t.ctrlKey && (i |= 16), i;
369
+ }
370
+ /**
371
+ * Encode mouse event as SGR sequence
372
+ * SGR format: \x1b[<Btn;Col;RowM (press/motion) or \x1b[<Btn;Col;Rowm (release)
373
+ */
374
+ encodeMouseSGR(t, i, e, s, r) {
375
+ return `\x1B[<${t + r};${i};${e}${s ? "m" : "M"}`;
376
+ }
377
+ /**
378
+ * Encode mouse event as X10/normal sequence (legacy format)
379
+ * Format: \x1b[M<Btn+32><Col+32><Row+32>
380
+ */
381
+ encodeMouseX10(t, i, e, s) {
382
+ const r = t + s + 32, n = String.fromCharCode(Math.min(i + 32, 255)), o = String.fromCharCode(Math.min(e + 32, 255));
383
+ return `\x1B[M${String.fromCharCode(r)}${n}${o}`;
384
+ }
385
+ /**
386
+ * Send mouse event to terminal
387
+ */
388
+ sendMouseEvent(t, i, e, s, r) {
389
+ var l, h;
390
+ const n = this.getMouseModifiers(r), o = ((h = (l = this.mouseConfig) == null ? void 0 : l.hasSgrMouseMode) == null ? void 0 : h.call(l)) ?? !0;
391
+ let a;
392
+ if (o)
393
+ a = this.encodeMouseSGR(t, i, e, s, n);
394
+ else {
395
+ const u = s ? 3 : t;
396
+ a = this.encodeMouseX10(u, i, e, n);
397
+ }
398
+ this.onDataCallback(a);
399
+ }
400
+ /**
401
+ * Handle mousedown event
402
+ */
403
+ handleMouseDown(t) {
404
+ var s;
405
+ if (this.isDisposed || !((s = this.mouseConfig) != null && s.hasMouseTracking()))
406
+ return;
407
+ const i = this.pixelToCell(t);
408
+ if (!i)
409
+ return;
410
+ const e = t.button;
411
+ this.mouseButtonsPressed |= 1 << e, this.sendMouseEvent(e, i.col, i.row, !1, t);
412
+ }
413
+ /**
414
+ * Handle mouseup event
415
+ */
416
+ handleMouseUp(t) {
417
+ var s;
418
+ if (this.isDisposed || !((s = this.mouseConfig) != null && s.hasMouseTracking()))
419
+ return;
420
+ const i = this.pixelToCell(t);
421
+ if (!i)
422
+ return;
423
+ const e = t.button;
424
+ this.mouseButtonsPressed &= ~(1 << e), this.sendMouseEvent(e, i.col, i.row, !0, t);
425
+ }
426
+ /**
427
+ * Handle mousemove event
428
+ */
429
+ handleMouseMove(t) {
430
+ var n, o, a;
431
+ if (this.isDisposed || !((n = this.mouseConfig) != null && n.hasMouseTracking()))
432
+ return;
433
+ const i = ((o = this.getModeCallback) == null ? void 0 : o.call(this, 1002)) ?? !1, e = ((a = this.getModeCallback) == null ? void 0 : a.call(this, 1003)) ?? !1;
434
+ if (!i && !e || i && !e && this.mouseButtonsPressed === 0)
435
+ return;
436
+ const s = this.pixelToCell(t);
437
+ if (!s)
438
+ return;
439
+ let r = 32;
440
+ this.mouseButtonsPressed & 1 ? r += 0 : this.mouseButtonsPressed & 2 ? r += 1 : this.mouseButtonsPressed & 4 && (r += 2), this.sendMouseEvent(r, s.col, s.row, !1, t);
441
+ }
442
+ /**
443
+ * Handle wheel event (scroll)
444
+ */
445
+ handleWheel(t) {
446
+ var i;
447
+ this.isDisposed || (i = this.mouseConfig) != null && i.hasMouseTracking() && (this.sendWheelMouseEvent(t), t.preventDefault());
448
+ }
449
+ /**
450
+ * Send a wheel event as a mouse tracking sequence.
451
+ * Public so that Terminal can forward wheel events when mouse tracking is
452
+ * active (the Terminal-level capture handler stops propagation to prevent
453
+ * browser scrolling, so this method allows explicit forwarding).
454
+ */
455
+ handleWheelEvent(t) {
456
+ this.isDisposed || this.sendWheelMouseEvent(t);
457
+ }
458
+ /**
459
+ * Encode and send a wheel event as a mouse tracking escape sequence.
460
+ * Button 64 = scroll up, button 65 = scroll down, with cell coordinates.
461
+ */
462
+ sendWheelMouseEvent(t) {
463
+ const i = this.pixelToCell(t);
464
+ if (!i)
465
+ return;
466
+ const e = t.deltaY < 0 ? 64 : 65;
467
+ this.sendMouseEvent(e, i.col, i.row, !1, t);
468
+ }
469
+ /**
470
+ * Emit paste data with bracketed paste support
471
+ */
472
+ emitPasteData(t) {
473
+ var e;
474
+ ((e = this.getModeCallback) == null ? void 0 : e.call(this, 2004)) ?? !1 ? this.onDataCallback("\x1B[200~" + t + "\x1B[201~") : this.onDataCallback(t);
475
+ }
476
+ /**
477
+ * Record keydown data for beforeinput de-duplication
478
+ */
479
+ recordKeyDownData(t) {
480
+ this.lastKeyDownData = t, this.lastKeyDownTime = this.getNow();
481
+ }
482
+ /**
483
+ * Record paste data for beforeinput de-duplication
484
+ */
485
+ recordPasteData(t, i) {
486
+ this.lastPasteData = t, this.lastPasteTime = this.getNow(), this.lastPasteSource = i;
487
+ }
488
+ /**
489
+ * Check if beforeinput should be ignored due to a recent keydown
490
+ */
491
+ shouldIgnoreBeforeInput(t) {
492
+ if (!this.lastKeyDownData)
493
+ return !1;
494
+ const e = this.getNow() - this.lastKeyDownTime < L.BEFORE_INPUT_IGNORE_MS && this.lastKeyDownData === t;
495
+ return this.lastKeyDownData = null, e;
496
+ }
497
+ /**
498
+ * Check if beforeinput text should be ignored due to a recent composition end
499
+ */
500
+ shouldIgnoreBeforeInputFromComposition(t) {
501
+ if (!this.lastCompositionData)
502
+ return !1;
503
+ const e = this.getNow() - this.lastCompositionTime < L.BEFORE_INPUT_IGNORE_MS && this.lastCompositionData === t;
504
+ return e && (this.lastCompositionData = null), e;
505
+ }
506
+ /**
507
+ * Check if composition end should be ignored due to a recent beforeinput text
508
+ */
509
+ shouldIgnoreCompositionEnd(t) {
510
+ if (!this.lastBeforeInputData)
511
+ return !1;
512
+ const e = this.getNow() - this.lastBeforeInputTime < L.BEFORE_INPUT_IGNORE_MS && this.lastBeforeInputData === t;
513
+ return e && (this.lastBeforeInputData = null), e;
514
+ }
515
+ /**
516
+ * Record beforeinput text for composition de-duplication
517
+ */
518
+ recordBeforeInputData(t) {
519
+ this.lastBeforeInputData = t, this.lastBeforeInputTime = this.getNow();
520
+ }
521
+ /**
522
+ * Record composition end data for beforeinput de-duplication
523
+ */
524
+ recordCompositionData(t) {
525
+ this.lastCompositionData = t, this.lastCompositionTime = this.getNow();
526
+ }
527
+ /**
528
+ * Check if paste should be ignored due to a recent paste event from another source
529
+ */
530
+ shouldIgnorePasteEvent(t, i) {
531
+ if (!this.lastPasteData || this.lastPasteSource === i)
532
+ return !1;
533
+ const s = this.getNow() - this.lastPasteTime < L.BEFORE_INPUT_IGNORE_MS && this.lastPasteData === t;
534
+ return s && (this.lastPasteData = null, this.lastPasteSource = null), s;
535
+ }
536
+ /**
537
+ * Get current time in milliseconds
538
+ */
539
+ getNow() {
540
+ return typeof performance < "u" && typeof performance.now == "function" ? performance.now() : Date.now();
541
+ }
542
+ /**
543
+ * Dispose the InputHandler and remove event listeners
544
+ */
545
+ dispose() {
546
+ if (this.isDisposed)
547
+ return;
548
+ this.keydownListener && (this.container.removeEventListener("keydown", this.keydownListener), this.keydownListener = null), this.keypressListener && (this.container.removeEventListener("keypress", this.keypressListener), this.keypressListener = null), this.pasteListener && (this.container.removeEventListener("paste", this.pasteListener), this.inputElement && this.inputElement !== this.container && this.inputElement.removeEventListener("paste", this.pasteListener), this.pasteListener = null), this.beforeInputListener && this.inputElement && (this.inputElement.removeEventListener("beforeinput", this.beforeInputListener), this.beforeInputListener = null);
549
+ const t = this.inputElement || this.container;
550
+ this.compositionStartListener && (t.removeEventListener("compositionstart", this.compositionStartListener), this.compositionStartListener = null), this.compositionUpdateListener && (t.removeEventListener("compositionupdate", this.compositionUpdateListener), this.compositionUpdateListener = null), this.compositionEndListener && (t.removeEventListener("compositionend", this.compositionEndListener), this.compositionEndListener = null), this.mousedownListener && (this.container.removeEventListener("mousedown", this.mousedownListener), this.mousedownListener = null), this.mouseupListener && (this.container.removeEventListener("mouseup", this.mouseupListener), this.mouseupListener = null), this.mousemoveListener && (this.container.removeEventListener("mousemove", this.mousemoveListener), this.mousemoveListener = null), this.wheelListener && (this.container.removeEventListener("wheel", this.wheelListener), this.wheelListener = null), this.isDisposed = !0;
551
+ }
552
+ /**
553
+ * Check if handler is disposed
554
+ */
555
+ isActive() {
556
+ return !this.isDisposed;
557
+ }
558
+ };
559
+ V.BEFORE_INPUT_IGNORE_MS = 100;
560
+ let Q = V;
561
+ class X {
562
+ // Terminal instance for buffer access
563
+ constructor(t) {
564
+ this.terminal = t, this.providers = [], this.linkCache = /* @__PURE__ */ new Map(), this.scannedRows = /* @__PURE__ */ new Set();
565
+ }
566
+ /**
567
+ * Register a link provider
568
+ */
569
+ registerProvider(t) {
570
+ this.providers.push(t), this.invalidateCache();
571
+ }
572
+ /**
573
+ * Get link at the specified buffer position
574
+ * @param col Column (0-based)
575
+ * @param row Absolute row in buffer (0-based)
576
+ * @returns Link at position, or undefined if none
577
+ */
578
+ async getLinkAt(t, i) {
579
+ const e = this.terminal.buffer.active.getLine(i);
580
+ if (!(!e || t < 0 || t >= e.length || !e.getCell(t))) {
581
+ for (const r of this.linkCache.values())
582
+ if (this.isPositionInLink(t, i, r))
583
+ return r;
584
+ this.scannedRows.has(i) || await this.scanRow(i);
585
+ for (const r of this.linkCache.values())
586
+ if (this.isPositionInLink(t, i, r))
587
+ return r;
588
+ }
589
+ }
590
+ /**
591
+ * Scan a row for links using all registered providers
592
+ */
593
+ async scanRow(t) {
594
+ this.scannedRows.add(t);
595
+ const i = [];
596
+ for (const e of this.providers) {
597
+ const s = await new Promise((r) => {
598
+ e.provideLinks(t, r);
599
+ });
600
+ s && i.push(...s);
601
+ }
602
+ for (const e of i)
603
+ this.cacheLink(e);
604
+ }
605
+ /**
606
+ * Cache a link for fast lookup
607
+ *
608
+ * Note: We cache by position range, not hyperlink_id, because the WASM
609
+ * returns hyperlink_id as a boolean (0 or 1), not a unique identifier.
610
+ * The actual unique identifier is the URI which is retrieved separately.
611
+ */
612
+ cacheLink(t) {
613
+ const { start: i, end: e } = t.range, s = `r${i.y}:${i.x}-${e.x}`;
614
+ this.linkCache.has(s) || this.linkCache.set(s, t);
615
+ }
616
+ /**
617
+ * Check if a position is within a link's range
618
+ */
619
+ isPositionInLink(t, i, e) {
620
+ const { start: s, end: r } = e.range;
621
+ return i < s.y || i > r.y ? !1 : s.y === r.y ? t >= s.x && t <= r.x : i === s.y ? t >= s.x : i === r.y ? t <= r.x : !0;
622
+ }
623
+ /**
624
+ * Invalidate cache when terminal content changes
625
+ * Should be called on terminal write, resize, or clear
626
+ */
627
+ invalidateCache() {
628
+ this.linkCache.clear(), this.scannedRows.clear();
629
+ }
630
+ /**
631
+ * Invalidate cache for specific rows
632
+ * Used when only part of the terminal changed
633
+ */
634
+ invalidateRows(t, i) {
635
+ for (let s = t; s <= i; s++)
636
+ this.scannedRows.delete(s);
637
+ const e = [];
638
+ for (const [s, r] of this.linkCache.entries()) {
639
+ const { start: n, end: o } = r.range;
640
+ (n.y >= t && n.y <= i || o.y >= t && o.y <= i || n.y < t && o.y > i) && e.push(s);
641
+ }
642
+ for (const s of e)
643
+ this.linkCache.delete(s);
644
+ }
645
+ /**
646
+ * Dispose and cleanup
647
+ */
648
+ dispose() {
649
+ var t;
650
+ this.linkCache.clear(), this.scannedRows.clear();
651
+ for (const i of this.providers)
652
+ (t = i.dispose) == null || t.call(i);
653
+ this.providers = [];
654
+ }
655
+ }
656
+ class J {
657
+ constructor(t) {
658
+ this.terminal = t;
659
+ }
660
+ /**
661
+ * Provide all OSC 8 links on the given row
662
+ * Note: This may return links that span multiple rows
663
+ */
664
+ provideLinks(t, i) {
665
+ const e = [], s = /* @__PURE__ */ new Set(), r = this.terminal.buffer.active.getLine(t);
666
+ if (!r) {
667
+ i(void 0);
668
+ return;
669
+ }
670
+ for (let n = 0; n < r.length; n++) {
671
+ if (s.has(n))
672
+ continue;
673
+ const o = r.getCell(n);
674
+ if (!o || o.getHyperlinkId() === 0 || !this.terminal.wasmTerm)
675
+ continue;
676
+ const l = this.terminal.wasmTerm.getScrollbackLength(), h = t - l;
677
+ let u;
678
+ if (h < 0 ? u = this.terminal.wasmTerm.getScrollbackHyperlinkUri(t, n) : u = this.terminal.wasmTerm.getHyperlinkUri(h, n), u) {
679
+ let d = n;
680
+ for (let m = n + 1; m < r.length; m++) {
681
+ const g = r.getCell(m);
682
+ if (!g || g.getHyperlinkId() === 0 || (h < 0 ? this.terminal.wasmTerm.getScrollbackHyperlinkUri(t, m) : this.terminal.wasmTerm.getHyperlinkUri(h, m)) !== u)
683
+ break;
684
+ d = m;
685
+ }
686
+ for (let m = n; m <= d; m++)
687
+ s.add(m);
688
+ const p = {
689
+ start: { x: n, y: t },
690
+ end: { x: d, y: t }
691
+ };
692
+ e.push({
693
+ text: u,
694
+ range: p,
695
+ activate: (m) => {
696
+ (m.ctrlKey || m.metaKey) && window.open(u, "_blank", "noopener,noreferrer");
697
+ }
698
+ });
699
+ }
700
+ }
701
+ i(e.length > 0 ? e : void 0);
702
+ }
703
+ /**
704
+ * Find the full extent of a link by scanning for contiguous cells
705
+ * with the same hyperlink_id. Handles multi-line links.
706
+ */
707
+ findLinkRange(t, i, e) {
708
+ const s = this.terminal.buffer.active;
709
+ let r = i, n = e;
710
+ for (; n > 0; ) {
711
+ const h = s.getLine(r);
712
+ if (!h)
713
+ break;
714
+ const u = h.getCell(n - 1);
715
+ if (!u || u.getHyperlinkId() !== t)
716
+ break;
717
+ n--;
718
+ }
719
+ if (n === 0 && r > 0) {
720
+ let h = r - 1;
721
+ for (; h >= 0; ) {
722
+ const u = s.getLine(h);
723
+ if (!u || u.length === 0)
724
+ break;
725
+ const d = u.getCell(u.length - 1);
726
+ if (!d || d.getHyperlinkId() !== t)
727
+ break;
728
+ r = h, n = 0;
729
+ for (let p = u.length - 1; p >= 0; p--) {
730
+ const m = u.getCell(p);
731
+ if (!m || m.getHyperlinkId() !== t) {
732
+ n = p + 1;
733
+ break;
734
+ }
735
+ }
736
+ if (n === 0)
737
+ h--;
738
+ else
739
+ break;
740
+ }
741
+ }
742
+ let o = i, a = e;
743
+ const l = s.getLine(o);
744
+ if (l) {
745
+ for (; a < l.length - 1; ) {
746
+ const h = l.getCell(a + 1);
747
+ if (!h || h.getHyperlinkId() !== t)
748
+ break;
749
+ a++;
750
+ }
751
+ if (a === l.length - 1) {
752
+ let h = o + 1;
753
+ const u = s.length;
754
+ for (; h < u; ) {
755
+ const d = s.getLine(h);
756
+ if (!d || d.length === 0)
757
+ break;
758
+ const p = d.getCell(0);
759
+ if (!p || p.getHyperlinkId() !== t)
760
+ break;
761
+ o = h, a = 0;
762
+ for (let m = 0; m < d.length; m++) {
763
+ const g = d.getCell(m);
764
+ if (!g)
765
+ break;
766
+ if (g.getHyperlinkId() !== t) {
767
+ a = m - 1;
768
+ break;
769
+ }
770
+ a = m;
771
+ }
772
+ if (a === d.length - 1)
773
+ h++;
774
+ else
775
+ break;
776
+ }
777
+ }
778
+ }
779
+ return {
780
+ start: { x: n, y: r },
781
+ end: { x: a, y: o }
782
+ };
783
+ }
784
+ dispose() {
785
+ }
786
+ }
787
+ const B = class R {
788
+ constructor(t) {
789
+ this.terminal = t;
790
+ }
791
+ /**
792
+ * Provide all regex-detected URLs on the given row
793
+ */
794
+ provideLinks(t, i) {
795
+ const e = [], s = this.terminal.buffer.active.getLine(t);
796
+ if (!s) {
797
+ i(void 0);
798
+ return;
799
+ }
800
+ const r = this.lineToText(s);
801
+ R.URL_REGEX.lastIndex = 0;
802
+ let n = R.URL_REGEX.exec(r);
803
+ for (; n !== null; ) {
804
+ let o = n[0];
805
+ const a = n.index;
806
+ let l = n.index + o.length - 1;
807
+ const h = o.replace(R.TRAILING_PUNCTUATION, "");
808
+ for (h.length < o.length && (o = h, l = a + o.length - 1); o.endsWith(")"); ) {
809
+ const u = o.split("(").length - 1;
810
+ if (o.split(")").length - 1 > u)
811
+ o = o.slice(0, -1), l--;
812
+ else
813
+ break;
814
+ }
815
+ o.length > 8 && e.push({
816
+ text: o,
817
+ range: {
818
+ start: { x: a, y: t },
819
+ end: { x: l, y: t }
820
+ },
821
+ activate: (u) => {
822
+ (u.ctrlKey || u.metaKey) && window.open(o, "_blank", "noopener,noreferrer");
823
+ }
824
+ }), n = R.URL_REGEX.exec(r);
825
+ }
826
+ i(e.length > 0 ? e : void 0);
827
+ }
828
+ /**
829
+ * Convert a buffer line to plain text string
830
+ */
831
+ lineToText(t) {
832
+ const i = [];
833
+ for (let e = 0; e < t.length; e++) {
834
+ const s = t.getCell(e);
835
+ if (!s) {
836
+ i.push(" ");
837
+ continue;
838
+ }
839
+ const r = s.getCodepoint();
840
+ r === 0 || r < 32 ? i.push(" ") : i.push(String.fromCodePoint(r));
841
+ }
842
+ return i.join("");
843
+ }
844
+ dispose() {
845
+ }
846
+ };
847
+ B.URL_REGEX = /(?:https?:\/\/|mailto:|ftp:\/\/|ssh:\/\/|git:\/\/|tel:|magnet:|gemini:\/\/|gopher:\/\/|news:)[\w\-.~:\/?#@!$&*+,;=%()]+/gi;
848
+ B.TRAILING_PUNCTUATION = /[.,;!?\]]+$/;
849
+ let j = B;
850
+ const Z = [
851
+ 773,
852
+ 781,
853
+ 782,
854
+ 784,
855
+ 786,
856
+ 829,
857
+ 830,
858
+ 831,
859
+ 838,
860
+ 842,
861
+ 843,
862
+ 844,
863
+ 848,
864
+ 849,
865
+ 850,
866
+ 855,
867
+ 859,
868
+ 867,
869
+ 868,
870
+ 869,
871
+ 870,
872
+ 871,
873
+ 872,
874
+ 873,
875
+ 874,
876
+ 875,
877
+ 876,
878
+ 877,
879
+ 878,
880
+ 879,
881
+ 1155,
882
+ 1156,
883
+ 1157,
884
+ 1158,
885
+ 1159,
886
+ 1426,
887
+ 1427,
888
+ 1428,
889
+ 1429,
890
+ 1431,
891
+ 1432,
892
+ 1433,
893
+ 1436,
894
+ 1437,
895
+ 1438,
896
+ 1439,
897
+ 1440,
898
+ 1441,
899
+ 1448,
900
+ 1449,
901
+ 1451,
902
+ 1452,
903
+ 1455,
904
+ 1476,
905
+ 1552,
906
+ 1553,
907
+ 1554,
908
+ 1555,
909
+ 1556,
910
+ 1557,
911
+ 1558,
912
+ 1559,
913
+ 1623,
914
+ 1624,
915
+ 1625,
916
+ 1626,
917
+ 1627,
918
+ 1629,
919
+ 1630,
920
+ 1750,
921
+ 1751,
922
+ 1752,
923
+ 1753,
924
+ 1754,
925
+ 1755,
926
+ 1756,
927
+ 1759,
928
+ 1760,
929
+ 1761,
930
+ 1762,
931
+ 1764,
932
+ 1767,
933
+ 1768,
934
+ 1771,
935
+ 1772,
936
+ 1840,
937
+ 1842,
938
+ 1843,
939
+ 1845,
940
+ 1846,
941
+ 1850,
942
+ 1853,
943
+ 1855,
944
+ 1856,
945
+ 1857,
946
+ 1859,
947
+ 1861,
948
+ 1863,
949
+ 1865,
950
+ 1866,
951
+ 2027,
952
+ 2028,
953
+ 2029,
954
+ 2030,
955
+ 2031,
956
+ 2032,
957
+ 2033,
958
+ 2035,
959
+ 2070,
960
+ 2071,
961
+ 2072,
962
+ 2073,
963
+ 2075,
964
+ 2076,
965
+ 2077,
966
+ 2078,
967
+ 2079,
968
+ 2080,
969
+ 2081,
970
+ 2082,
971
+ 2083,
972
+ 2085,
973
+ 2086,
974
+ 2087,
975
+ 2089,
976
+ 2090,
977
+ 2091,
978
+ 2092,
979
+ 2093,
980
+ 2385,
981
+ 2387,
982
+ 2388,
983
+ 3970,
984
+ 3971,
985
+ 3974,
986
+ 3975,
987
+ 4957,
988
+ 4958,
989
+ 4959,
990
+ 6109,
991
+ 6458,
992
+ 6679,
993
+ 6773,
994
+ 6774,
995
+ 6775,
996
+ 6776,
997
+ 6777,
998
+ 6778,
999
+ 6779,
1000
+ 6780,
1001
+ 7019,
1002
+ 7021,
1003
+ 7022,
1004
+ 7023,
1005
+ 7024,
1006
+ 7025,
1007
+ 7026,
1008
+ 7027,
1009
+ 7376,
1010
+ 7377,
1011
+ 7378,
1012
+ 7386,
1013
+ 7387,
1014
+ 7392,
1015
+ 7616,
1016
+ 7617,
1017
+ 7619,
1018
+ 7620,
1019
+ 7621,
1020
+ 7622,
1021
+ 7623,
1022
+ 7624,
1023
+ 7625,
1024
+ 7627,
1025
+ 7628,
1026
+ 7633,
1027
+ 7634,
1028
+ 7635,
1029
+ 7636,
1030
+ 7637,
1031
+ 7638,
1032
+ 7639,
1033
+ 7640,
1034
+ 7641,
1035
+ 7642,
1036
+ 7643,
1037
+ 7644,
1038
+ 7645,
1039
+ 7646,
1040
+ 7647,
1041
+ 7648,
1042
+ 7649,
1043
+ 7650,
1044
+ 7651,
1045
+ 7652,
1046
+ 7653,
1047
+ 7654,
1048
+ 7678,
1049
+ 8400,
1050
+ 8401,
1051
+ 8404,
1052
+ 8405,
1053
+ 8406,
1054
+ 8407,
1055
+ 8411,
1056
+ 8412,
1057
+ 8417,
1058
+ 8423,
1059
+ 8425,
1060
+ 8432,
1061
+ 11503,
1062
+ 11504,
1063
+ 11505,
1064
+ 11744,
1065
+ 11745,
1066
+ 11746,
1067
+ 11747,
1068
+ 11748,
1069
+ 11749,
1070
+ 11750,
1071
+ 11751,
1072
+ 11752,
1073
+ 11753,
1074
+ 11754,
1075
+ 11755,
1076
+ 11756,
1077
+ 11757,
1078
+ 11758,
1079
+ 11759,
1080
+ 11760,
1081
+ 11761,
1082
+ 11762,
1083
+ 11763,
1084
+ 11764,
1085
+ 11765,
1086
+ 11766,
1087
+ 11767,
1088
+ 11768,
1089
+ 11769,
1090
+ 11770,
1091
+ 11771,
1092
+ 11772,
1093
+ 11773,
1094
+ 11774,
1095
+ 11775,
1096
+ 42607,
1097
+ 42620,
1098
+ 42621,
1099
+ 42736,
1100
+ 42737,
1101
+ 43232,
1102
+ 43233,
1103
+ 43234,
1104
+ 43235,
1105
+ 43236,
1106
+ 43237,
1107
+ 43238,
1108
+ 43239,
1109
+ 43240,
1110
+ 43241,
1111
+ 43242,
1112
+ 43243,
1113
+ 43244,
1114
+ 43245,
1115
+ 43246,
1116
+ 43247,
1117
+ 43248,
1118
+ 43249,
1119
+ 43696,
1120
+ 43698,
1121
+ 43699,
1122
+ 43703,
1123
+ 43704,
1124
+ 43710,
1125
+ 43711,
1126
+ 43713,
1127
+ 65056,
1128
+ 65057,
1129
+ 65058,
1130
+ 65059,
1131
+ 65060,
1132
+ 65061,
1133
+ 65062,
1134
+ 68111,
1135
+ 68152,
1136
+ 119173,
1137
+ 119174,
1138
+ 119175,
1139
+ 119176,
1140
+ 119177,
1141
+ 119210,
1142
+ 119211,
1143
+ 119212,
1144
+ 119213,
1145
+ 119362,
1146
+ 119363,
1147
+ 119364
1148
+ ], tt = new Map(Z.map((y, t) => [y, t]));
1149
+ function H(y) {
1150
+ return tt.get(y) ?? -1;
1151
+ }
1152
+ const et = 1109742, E = {
1153
+ foreground: "#d4d4d4",
1154
+ background: "#1e1e1e",
1155
+ cursor: "#ffffff",
1156
+ cursorAccent: "#1e1e1e",
1157
+ // Selection colors: solid colors that replace cell bg/fg when selected
1158
+ // Using Ghostty's approach: selection bg = default fg, selection fg = default bg
1159
+ selectionBackground: "#d4d4d4",
1160
+ selectionForeground: "#1e1e1e",
1161
+ black: "#000000",
1162
+ red: "#cd3131",
1163
+ green: "#0dbc79",
1164
+ yellow: "#e5e510",
1165
+ blue: "#2472c8",
1166
+ magenta: "#bc3fbc",
1167
+ cyan: "#11a8cd",
1168
+ white: "#e5e5e5",
1169
+ brightBlack: "#666666",
1170
+ brightRed: "#f14c4c",
1171
+ brightGreen: "#23d18b",
1172
+ brightYellow: "#f5f543",
1173
+ brightBlue: "#3b8eea",
1174
+ brightMagenta: "#d670d6",
1175
+ brightCyan: "#29b8db",
1176
+ brightWhite: "#ffffff"
1177
+ };
1178
+ function N(y, t) {
1179
+ return y.width === t.width && y.height === t.height && y.format === t.format && y.dataPtr === t.data.byteOffset && y.dataLen === t.data.length;
1180
+ }
1181
+ class it {
1182
+ constructor(t, i = {}) {
1183
+ this.cursorVisible = !0, this.lastCursorPosition = { x: 0, y: 0 }, this.onRequestRender = null, this.lastViewportY = 0, this.currentBuffer = null, this.kittyImageCache = /* @__PURE__ */ new Map(), this.kittyVirtualPlacements = /* @__PURE__ */ new Map(), this.currentDirectPlacements = [], this.lastKittyDirectSigs = /* @__PURE__ */ new Map(), this.kittyDamagedRows = /* @__PURE__ */ new Set(), this.currentRenderBuffer = null, this.currentKittyGraphics = null, this.currentSelectionCoords = null, this.hoveredHyperlinkId = 0, this.previousHoveredHyperlinkId = 0, this.hoveredLinkRange = null, this.previousHoveredLinkRange = null, this.canvas = t;
1184
+ const e = t.getContext("2d", { alpha: !0 });
1185
+ if (!e)
1186
+ throw new Error("Failed to get 2D rendering context");
1187
+ this.ctx = e, this.fontSize = i.fontSize ?? 15, this.fontFamily = i.fontFamily ?? "monospace", this.cursorStyle = i.cursorStyle ?? "block", this.cursorBlink = i.cursorBlink ?? !1, this.theme = { ...E, ...i.theme }, this.devicePixelRatio = i.devicePixelRatio ?? window.devicePixelRatio ?? 1, this.palette = [
1188
+ this.theme.black,
1189
+ this.theme.red,
1190
+ this.theme.green,
1191
+ this.theme.yellow,
1192
+ this.theme.blue,
1193
+ this.theme.magenta,
1194
+ this.theme.cyan,
1195
+ this.theme.white,
1196
+ this.theme.brightBlack,
1197
+ this.theme.brightRed,
1198
+ this.theme.brightGreen,
1199
+ this.theme.brightYellow,
1200
+ this.theme.brightBlue,
1201
+ this.theme.brightMagenta,
1202
+ this.theme.brightCyan,
1203
+ this.theme.brightWhite
1204
+ ], this.metrics = this.measureFont(), this.cursorBlink && this.startCursorBlink();
1205
+ }
1206
+ // ==========================================================================
1207
+ // Font Metrics Measurement
1208
+ // ==========================================================================
1209
+ /**
1210
+ * Build a CSS font string with proper quoting for font families with spaces.
1211
+ * Example: "Fira Code, monospace" -> '"Fira Code", monospace'
1212
+ */
1213
+ buildFontString(t = "") {
1214
+ const i = this.fontFamily.split(",").map((e) => {
1215
+ const s = e.trim();
1216
+ return s.startsWith('"') || s.startsWith("'") || !s.includes(" ") ? s : `"${s}"`;
1217
+ }).join(", ");
1218
+ return `${t}${this.fontSize}px ${i}`;
1219
+ }
1220
+ measureFont() {
1221
+ const i = document.createElement("canvas").getContext("2d");
1222
+ i.font = this.buildFontString();
1223
+ const e = i.measureText("M"), s = e.fontBoundingBoxAscent || e.actualBoundingBoxAscent || this.fontSize * 0.8, r = e.fontBoundingBoxDescent || e.actualBoundingBoxDescent || this.fontSize * 0.2, n = this.devicePixelRatio, o = Math.ceil(e.width * n) / n, a = Math.ceil((s + r) * n) / n, l = Math.ceil(s * n) / n;
1224
+ return { width: o, height: a, baseline: l };
1225
+ }
1226
+ /**
1227
+ * Remeasure font metrics (call after font loads or changes)
1228
+ */
1229
+ remeasureFont() {
1230
+ this.metrics = this.measureFont();
1231
+ }
1232
+ // ==========================================================================
1233
+ // Color Conversion
1234
+ // ==========================================================================
1235
+ rgbToCSS(t, i, e) {
1236
+ return `rgb(${t}, ${i}, ${e})`;
1237
+ }
1238
+ // ==========================================================================
1239
+ // Canvas Sizing
1240
+ // ==========================================================================
1241
+ /**
1242
+ * Resize canvas to fit terminal dimensions
1243
+ */
1244
+ resize(t, i) {
1245
+ const e = t * this.metrics.width, s = i * this.metrics.height;
1246
+ this.canvas.style.width = `${e}px`, this.canvas.style.height = `${s}px`, this.canvas.width = e * this.devicePixelRatio, this.canvas.height = s * this.devicePixelRatio, this.ctx.scale(this.devicePixelRatio, this.devicePixelRatio), this.ctx.textBaseline = "alphabetic", this.ctx.textAlign = "left", this.ctx.fillStyle = this.theme.background, this.ctx.fillRect(0, 0, e, s);
1247
+ }
1248
+ // ==========================================================================
1249
+ // Main Rendering
1250
+ // ==========================================================================
1251
+ /**
1252
+ * Render the terminal buffer to canvas
1253
+ */
1254
+ render(t, i = !1, e = 0, s, r = 1) {
1255
+ var b;
1256
+ this.currentBuffer = t, this.currentRenderBuffer = t;
1257
+ const n = t.getCursor(), o = t.getDimensions();
1258
+ this.precomputeKittyState(t, o.rows);
1259
+ const a = s ? s.getScrollbackLength() : 0;
1260
+ (b = t.needsFullRedraw) != null && b.call(t) && (i = !0), (this.canvas.width !== o.cols * this.metrics.width * this.devicePixelRatio || this.canvas.height !== o.rows * this.metrics.height * this.devicePixelRatio) && (this.resize(o.cols, o.rows), i = !0), e !== this.lastViewportY && (i = !0, this.lastViewportY = e);
1261
+ const h = n.x !== this.lastCursorPosition.x || n.y !== this.lastCursorPosition.y;
1262
+ if (h || this.cursorBlink) {
1263
+ if (!i && !t.isRowDirty(n.y)) {
1264
+ const f = t.getLine(n.y);
1265
+ f && this.renderLine(f, n.y, o.cols);
1266
+ }
1267
+ if (h && !i) {
1268
+ const f = t.getLine(this.lastCursorPosition.y);
1269
+ f && this.renderLine(f, this.lastCursorPosition.y, o.cols);
1270
+ }
1271
+ }
1272
+ const u = this.selectionManager && this.selectionManager.hasSelection(), d = /* @__PURE__ */ new Set();
1273
+ if (this.currentSelectionCoords = u ? this.selectionManager.getSelectionCoords() : null, this.currentSelectionCoords) {
1274
+ const f = this.currentSelectionCoords;
1275
+ for (let v = f.startRow; v <= f.endRow; v++)
1276
+ d.add(v);
1277
+ }
1278
+ if (this.selectionManager) {
1279
+ const f = this.selectionManager.getDirtySelectionRows();
1280
+ if (f.size > 0) {
1281
+ for (const v of f)
1282
+ d.add(v);
1283
+ this.selectionManager.clearDirtySelectionRows();
1284
+ }
1285
+ }
1286
+ const p = /* @__PURE__ */ new Set(), m = this.hoveredHyperlinkId !== this.previousHoveredHyperlinkId, g = JSON.stringify(this.hoveredLinkRange) !== JSON.stringify(this.previousHoveredLinkRange);
1287
+ if (m) {
1288
+ for (let f = 0; f < o.rows; f++) {
1289
+ let v = null;
1290
+ if (e > 0)
1291
+ if (f < e && s) {
1292
+ const C = a - Math.floor(e) + f;
1293
+ v = s.getScrollbackLine(C);
1294
+ } else {
1295
+ const C = f - Math.floor(e);
1296
+ v = t.getLine(C);
1297
+ }
1298
+ else
1299
+ v = t.getLine(f);
1300
+ if (v) {
1301
+ for (const C of v)
1302
+ if (C.hyperlink_id === this.hoveredHyperlinkId || C.hyperlink_id === this.previousHoveredHyperlinkId) {
1303
+ p.add(f);
1304
+ break;
1305
+ }
1306
+ }
1307
+ }
1308
+ this.previousHoveredHyperlinkId = this.hoveredHyperlinkId;
1309
+ }
1310
+ if (g) {
1311
+ if (this.previousHoveredLinkRange)
1312
+ for (let f = this.previousHoveredLinkRange.startY; f <= this.previousHoveredLinkRange.endY; f++)
1313
+ p.add(f);
1314
+ if (this.hoveredLinkRange)
1315
+ for (let f = this.hoveredLinkRange.startY; f <= this.hoveredLinkRange.endY; f++)
1316
+ p.add(f);
1317
+ this.previousHoveredLinkRange = this.hoveredLinkRange;
1318
+ }
1319
+ let w = !1;
1320
+ const S = /* @__PURE__ */ new Set();
1321
+ for (let f = 0; f < o.rows; f++)
1322
+ (e > 0 ? !0 : i || t.isRowDirty(f) || d.has(f) || p.has(f) || this.kittyDamagedRows.has(f)) && (S.add(f), f > 0 && S.add(f - 1), f < o.rows - 1 && S.add(f + 1));
1323
+ for (let f = 0; f < o.rows; f++) {
1324
+ if (!S.has(f))
1325
+ continue;
1326
+ w = !0;
1327
+ let v = null;
1328
+ if (e > 0)
1329
+ if (f < e && s) {
1330
+ const C = a - Math.floor(e) + f;
1331
+ v = s.getScrollbackLine(C);
1332
+ } else {
1333
+ const C = e > 0 ? f - Math.floor(e) : f;
1334
+ v = t.getLine(C);
1335
+ }
1336
+ else
1337
+ v = t.getLine(f);
1338
+ v && this.renderLine(v, f, o.cols);
1339
+ }
1340
+ if (this.currentDirectPlacements.length > 0 && w && this.renderKittyImages(), e === 0 && n.visible && this.cursorVisible) {
1341
+ const f = n.style ?? this.cursorStyle;
1342
+ this.renderCursor(n.x, n.y, f);
1343
+ }
1344
+ s && r > 0 && this.renderScrollbar(e, a, o.rows, r), this.lastCursorPosition = { x: n.x, y: n.y }, t.clearDirty();
1345
+ }
1346
+ /**
1347
+ * Render a single line using two-pass approach:
1348
+ * 1. First pass: Draw all cell backgrounds
1349
+ * 2. Second pass: Draw all cell text and decorations
1350
+ *
1351
+ * This two-pass approach is necessary for proper rendering of complex scripts
1352
+ * like Devanagari where diacritics (like vowel sign ि) can extend LEFT of the
1353
+ * base character into the previous cell's visual area. If we draw backgrounds
1354
+ * and text in a single pass (cell by cell), the background of cell N would
1355
+ * cover any left-extending portions of graphemes from cell N-1.
1356
+ */
1357
+ renderLine(t, i, e) {
1358
+ const s = i * this.metrics.height, r = e * this.metrics.width;
1359
+ this.ctx.clearRect(0, s, r, this.metrics.height), this.ctx.fillStyle = this.theme.background, this.ctx.fillRect(0, s, r, this.metrics.height);
1360
+ for (let n = 0; n < t.length; n++) {
1361
+ const o = t[n];
1362
+ o.width !== 0 && this.renderCellBackground(o, n, i);
1363
+ }
1364
+ for (let n = 0; n < t.length; n++) {
1365
+ const o = t[n];
1366
+ o.width !== 0 && this.renderCellText(o, n, i);
1367
+ }
1368
+ }
1369
+ /**
1370
+ * Render a cell's background only (Pass 1 of two-pass rendering)
1371
+ * Selection highlighting is integrated here to avoid z-order issues with
1372
+ * complex glyphs (like Devanagari) that extend outside their cell bounds.
1373
+ */
1374
+ renderCellBackground(t, i, e) {
1375
+ const s = i * this.metrics.width, r = e * this.metrics.height, n = this.metrics.width * t.width;
1376
+ if (this.isInSelection(i, e)) {
1377
+ this.ctx.fillStyle = this.theme.selectionBackground, this.ctx.fillRect(s, r, n, this.metrics.height);
1378
+ return;
1379
+ }
1380
+ let a = t.bg_r, l = t.bg_g, h = t.bg_b;
1381
+ t.flags & x.INVERSE && (a = t.fg_r, l = t.fg_g, h = t.fg_b), (t.flags & x.INVERSE ? t.fgIsDefault : t.bgIsDefault) || (this.ctx.fillStyle = this.rgbToCSS(a, l, h), this.ctx.fillRect(s, r, n, this.metrics.height));
1382
+ }
1383
+ /**
1384
+ * Render a cell's text and decorations (Pass 2 of two-pass rendering)
1385
+ * Selection foreground color is applied here to match the selection background.
1386
+ */
1387
+ renderCellText(t, i, e, s) {
1388
+ var S;
1389
+ const r = i * this.metrics.width, n = e * this.metrics.height, o = this.metrics.width * t.width;
1390
+ if (t.codepoint === et && this.renderPlaceholderCell(t, i, e) || t.flags & x.INVISIBLE)
1391
+ return;
1392
+ const a = this.isInSelection(i, e);
1393
+ let l = "";
1394
+ t.flags & x.ITALIC && (l += "italic "), t.flags & x.BOLD && (l += "bold "), this.ctx.font = this.buildFontString(l);
1395
+ let h = t.fg_r, u = t.fg_g, d = t.fg_b;
1396
+ if (t.flags & x.INVERSE && (h = t.bg_r, u = t.bg_g, d = t.bg_b), s)
1397
+ this.ctx.fillStyle = s;
1398
+ else if (a)
1399
+ this.ctx.fillStyle = this.theme.selectionForeground;
1400
+ else {
1401
+ const b = t.flags & x.INVERSE ? t.bgIsDefault : t.fgIsDefault;
1402
+ this.ctx.fillStyle = b ? this.theme.foreground : this.rgbToCSS(h, u, d);
1403
+ }
1404
+ t.flags & x.FAINT && (this.ctx.globalAlpha = 0.5);
1405
+ const p = r, m = n + this.metrics.baseline;
1406
+ let g;
1407
+ t.grapheme_len > 0 && ((S = this.currentBuffer) != null && S.getGraphemeString) ? g = this.currentBuffer.getGraphemeString(e, i) : g = String.fromCodePoint(t.codepoint || 32);
1408
+ const w = t.codepoint || 32;
1409
+ if (this.renderBlockChar(w, r, n, o) || this.renderPowerlineGlyph(w, r, n, o) || this.ctx.fillText(g, p, m), t.flags & x.FAINT && (this.ctx.globalAlpha = 1), t.flags & x.UNDERLINE) {
1410
+ const b = n + this.metrics.baseline + 2;
1411
+ this.ctx.strokeStyle = this.ctx.fillStyle, this.ctx.lineWidth = 1, this.ctx.beginPath(), this.ctx.moveTo(r, b), this.ctx.lineTo(r + o, b), this.ctx.stroke();
1412
+ }
1413
+ if (t.flags & x.STRIKETHROUGH) {
1414
+ const b = n + this.metrics.height / 2;
1415
+ this.ctx.strokeStyle = this.ctx.fillStyle, this.ctx.lineWidth = 1, this.ctx.beginPath(), this.ctx.moveTo(r, b), this.ctx.lineTo(r + o, b), this.ctx.stroke();
1416
+ }
1417
+ if (t.hyperlink_id > 0 && t.hyperlink_id === this.hoveredHyperlinkId) {
1418
+ const f = n + this.metrics.baseline + 2;
1419
+ this.ctx.strokeStyle = "#4A90E2", this.ctx.lineWidth = 1, this.ctx.beginPath(), this.ctx.moveTo(r, f), this.ctx.lineTo(r + o, f), this.ctx.stroke();
1420
+ }
1421
+ if (this.hoveredLinkRange) {
1422
+ const b = this.hoveredLinkRange;
1423
+ if (e === b.startY && i >= b.startX && (e < b.endY || i <= b.endX) || e > b.startY && e < b.endY || e === b.endY && i <= b.endX && (e > b.startY || i >= b.startX)) {
1424
+ const v = n + this.metrics.baseline + 2;
1425
+ this.ctx.strokeStyle = "#4A90E2", this.ctx.lineWidth = 1, this.ctx.beginPath(), this.ctx.moveTo(r, v), this.ctx.lineTo(r + o, v), this.ctx.stroke();
1426
+ }
1427
+ }
1428
+ }
1429
+ /**
1430
+ * Render block drawing characters as filled rectangles for pixel-perfect rendering.
1431
+ * Returns true if the character was handled, false if it should be rendered as text.
1432
+ */
1433
+ renderBlockChar(t, i, e, s) {
1434
+ const r = this.metrics.height;
1435
+ switch (t) {
1436
+ case 9600:
1437
+ return this.ctx.fillRect(i, e, s, r / 2), !0;
1438
+ case 9601:
1439
+ return this.ctx.fillRect(i, e + r * 7 / 8, s, r / 8), !0;
1440
+ case 9602:
1441
+ return this.ctx.fillRect(i, e + r * 3 / 4, s, r / 4), !0;
1442
+ case 9603:
1443
+ return this.ctx.fillRect(i, e + r * 5 / 8, s, r * 3 / 8), !0;
1444
+ case 9604:
1445
+ return this.ctx.fillRect(i, e + r / 2, s, r / 2), !0;
1446
+ case 9605:
1447
+ return this.ctx.fillRect(i, e + r * 3 / 8, s, r * 5 / 8), !0;
1448
+ case 9606:
1449
+ return this.ctx.fillRect(i, e + r / 4, s, r * 3 / 4), !0;
1450
+ case 9607:
1451
+ return this.ctx.fillRect(i, e + r / 8, s, r * 7 / 8), !0;
1452
+ case 9608:
1453
+ return this.ctx.fillRect(i, e, s, r), !0;
1454
+ case 9609:
1455
+ return this.ctx.fillRect(i, e, s * 7 / 8, r), !0;
1456
+ case 9610:
1457
+ return this.ctx.fillRect(i, e, s * 3 / 4, r), !0;
1458
+ case 9611:
1459
+ return this.ctx.fillRect(i, e, s * 5 / 8, r), !0;
1460
+ case 9612:
1461
+ return this.ctx.fillRect(i, e, s / 2, r), !0;
1462
+ case 9613:
1463
+ return this.ctx.fillRect(i, e, s * 3 / 8, r), !0;
1464
+ case 9614:
1465
+ return this.ctx.fillRect(i, e, s / 4, r), !0;
1466
+ case 9615:
1467
+ return this.ctx.fillRect(i, e, s / 8, r), !0;
1468
+ case 9616:
1469
+ return this.ctx.fillRect(i + s / 2, e, s / 2, r), !0;
1470
+ case 9620:
1471
+ return this.ctx.fillRect(i, e, s, r / 8), !0;
1472
+ case 9621:
1473
+ return this.ctx.fillRect(i + s * 7 / 8, e, s / 8, r), !0;
1474
+ default:
1475
+ return !1;
1476
+ }
1477
+ }
1478
+ /**
1479
+ * Render Powerline glyphs as vector shapes for pixel-perfect cell height.
1480
+ * Powerline glyphs (U+E0B0-U+E0BF) are designed to span the full cell height,
1481
+ * but font rendering often makes them slightly taller/shorter than the cell.
1482
+ * Drawing them as paths ensures they exactly fill the cell bounds.
1483
+ * Returns true if the character was handled, false if it should be rendered as text.
1484
+ */
1485
+ renderPowerlineGlyph(t, i, e, s) {
1486
+ const r = this.metrics.height, n = this.ctx;
1487
+ switch (t) {
1488
+ case 57520:
1489
+ return n.beginPath(), n.moveTo(i, e), n.lineTo(i + s, e + r / 2), n.lineTo(i, e + r), n.closePath(), n.fill(), !0;
1490
+ case 57521:
1491
+ return n.beginPath(), n.moveTo(i, e), n.lineTo(i + s, e + r / 2), n.lineTo(i, e + r), n.strokeStyle = n.fillStyle, n.lineWidth = 1, n.stroke(), !0;
1492
+ case 57522:
1493
+ return n.beginPath(), n.moveTo(i + s, e), n.lineTo(i, e + r / 2), n.lineTo(i + s, e + r), n.closePath(), n.fill(), !0;
1494
+ case 57523:
1495
+ return n.beginPath(), n.moveTo(i + s, e), n.lineTo(i, e + r / 2), n.lineTo(i + s, e + r), n.strokeStyle = n.fillStyle, n.lineWidth = 1, n.stroke(), !0;
1496
+ case 57524:
1497
+ return n.beginPath(), n.moveTo(i, e), n.ellipse(
1498
+ i,
1499
+ e + r / 2,
1500
+ s,
1501
+ r / 2,
1502
+ 0,
1503
+ -Math.PI / 2,
1504
+ Math.PI / 2,
1505
+ !1
1506
+ ), n.closePath(), n.fill(), !0;
1507
+ case 57525:
1508
+ return n.beginPath(), n.moveTo(i, e), n.ellipse(
1509
+ i,
1510
+ e + r / 2,
1511
+ s,
1512
+ r / 2,
1513
+ 0,
1514
+ -Math.PI / 2,
1515
+ Math.PI / 2,
1516
+ !1
1517
+ ), n.strokeStyle = n.fillStyle, n.lineWidth = 1, n.stroke(), !0;
1518
+ case 57526:
1519
+ return n.beginPath(), n.moveTo(i + s, e), n.ellipse(
1520
+ i + s,
1521
+ e + r / 2,
1522
+ s,
1523
+ r / 2,
1524
+ 0,
1525
+ -Math.PI / 2,
1526
+ Math.PI / 2,
1527
+ !0
1528
+ ), n.closePath(), n.fill(), !0;
1529
+ case 57527:
1530
+ return n.beginPath(), n.moveTo(i + s, e), n.ellipse(
1531
+ i + s,
1532
+ e + r / 2,
1533
+ s,
1534
+ r / 2,
1535
+ 0,
1536
+ -Math.PI / 2,
1537
+ Math.PI / 2,
1538
+ !0
1539
+ ), n.strokeStyle = n.fillStyle, n.lineWidth = 1, n.stroke(), !0;
1540
+ default:
1541
+ return !1;
1542
+ }
1543
+ }
1544
+ /**
1545
+ * Composite all visible kitty graphics placements onto the canvas.
1546
+ * Cheap when no graphics are active (one method check, one terminal_get).
1547
+ * Decode work is amortized across frames via kittyImageCache.
1548
+ */
1549
+ /**
1550
+ * Walk the placement iterator once at frame start, partitioning the
1551
+ * results: virtual placements go into kittyVirtualPlacements (keyed
1552
+ * by image id) for placeholder-cell lookup; direct visible placements
1553
+ * stay implicit and get re-iterated by renderKittyImages later.
1554
+ *
1555
+ * Also caches the storage handle for renderPlaceholderCell so the
1556
+ * per-cell hot path doesn't have to re-resolve it.
1557
+ */
1558
+ precomputeKittyState(t, i) {
1559
+ var n;
1560
+ this.kittyVirtualPlacements.clear(), this.currentDirectPlacements = [], this.kittyDamagedRows.clear(), this.currentKittyGraphics = null;
1561
+ const e = /* @__PURE__ */ new Map(), s = this.metrics.height, r = (o, a) => {
1562
+ const l = Math.max(0, Math.floor(o)), h = Math.min(i, Math.ceil(o + a / s));
1563
+ for (let u = l; u < h; u++)
1564
+ this.kittyDamagedRows.add(u);
1565
+ };
1566
+ if (t.getKittyGraphics && t.iterPlacements) {
1567
+ const o = t.getKittyGraphics();
1568
+ if (o !== null) {
1569
+ this.currentKittyGraphics = o;
1570
+ for (const a of t.iterPlacements(o, !1)) {
1571
+ if (a.isVirtual) {
1572
+ this.kittyVirtualPlacements.set(a.imageId, a);
1573
+ continue;
1574
+ }
1575
+ this.currentDirectPlacements.push(a);
1576
+ const l = (n = t.getKittyImagePixels) == null ? void 0 : n.call(t, o, a.imageId), h = {
1577
+ viewportCol: a.viewportCol,
1578
+ viewportRow: a.viewportRow,
1579
+ pixelWidth: a.pixelWidth,
1580
+ pixelHeight: a.pixelHeight,
1581
+ sourceX: a.sourceX,
1582
+ sourceY: a.sourceY,
1583
+ sourceWidth: a.sourceWidth,
1584
+ sourceHeight: a.sourceHeight,
1585
+ imgWidth: (l == null ? void 0 : l.width) ?? 0,
1586
+ imgHeight: (l == null ? void 0 : l.height) ?? 0,
1587
+ imgFormat: (l == null ? void 0 : l.format) ?? 0,
1588
+ dataPtr: (l == null ? void 0 : l.data.byteOffset) ?? 0,
1589
+ dataLen: (l == null ? void 0 : l.data.length) ?? 0
1590
+ };
1591
+ e.set(a.imageId, h);
1592
+ const u = this.lastKittyDirectSigs.get(a.imageId);
1593
+ (!u || u.viewportCol !== h.viewportCol || u.viewportRow !== h.viewportRow || u.pixelWidth !== h.pixelWidth || u.pixelHeight !== h.pixelHeight || u.sourceX !== h.sourceX || u.sourceY !== h.sourceY || u.sourceWidth !== h.sourceWidth || u.sourceHeight !== h.sourceHeight || u.imgWidth !== h.imgWidth || u.imgHeight !== h.imgHeight || u.imgFormat !== h.imgFormat || u.dataPtr !== h.dataPtr || u.dataLen !== h.dataLen) && (r(h.viewportRow, h.pixelHeight), u && r(u.viewportRow, u.pixelHeight));
1594
+ }
1595
+ }
1596
+ }
1597
+ for (const [o, a] of this.lastKittyDirectSigs)
1598
+ e.has(o) || r(a.viewportRow, a.pixelHeight);
1599
+ this.lastKittyDirectSigs = e;
1600
+ }
1601
+ /**
1602
+ * Get (or decode + cache) the canvas-ready bitmap for a kitty image.
1603
+ * Returns null if the image isn't stored or decode fails. Shared by
1604
+ * renderKittyImages (direct placements) and renderPlaceholderCell
1605
+ * (unicode-placeholder cells).
1606
+ */
1607
+ getOrDecodeKittyImage(t, i, e) {
1608
+ var o;
1609
+ const s = this.kittyImageCache.get(e), r = (o = t.getKittyImagePixels) == null ? void 0 : o.call(t, i, e);
1610
+ if (!r)
1611
+ return (s == null ? void 0 : s.canvas) ?? null;
1612
+ if (s && N(s, r))
1613
+ return s.canvas;
1614
+ const n = this.decodeKittyImageToCanvas(r);
1615
+ return n ? (this.kittyImageCache.set(e, {
1616
+ canvas: n,
1617
+ width: r.width,
1618
+ height: r.height,
1619
+ format: r.format,
1620
+ dataPtr: r.data.byteOffset,
1621
+ dataLen: r.data.length
1622
+ }), n) : null;
1623
+ }
1624
+ /**
1625
+ * Render a Block Elements codepoint (U+2580..U+259F) as fillRect(s) in
1626
+ * the current fillStyle. Returns true if the codepoint is a handled
1627
+ * block element; false to fall through to fillText.
1628
+ *
1629
+ * Drawing block elements through the font produces ~1-device-px gaps
1630
+ * at cell edges at integer dpr because the rasterized glyph doesn't
1631
+ * exactly fill the cell box. In half-block image renderings (ansimage,
1632
+ * pixterm) those gaps line up into a visible cell grid. Native
1633
+ * terminals draw block elements programmatically for the same reason.
1634
+ *
1635
+ * The eighths blocks (U+2581..U+2587 lower; U+2589..U+258F left) and
1636
+ * full block (U+2588) are stripes of n/8 of the cell. Shading blocks
1637
+ * (U+2591..U+2593) modulate globalAlpha for 25/50/75% fill. Quadrant
1638
+ * blocks (U+2596..U+259F) split the cell into a 2x2 grid and fill
1639
+ * some subset.
1640
+ */
1641
+ renderBlockElement(t, i, e, s) {
1642
+ if (t < 9600 || t > 9631)
1643
+ return !1;
1644
+ const r = s, n = this.metrics.height;
1645
+ if (t === 9600)
1646
+ return this.ctx.fillRect(i, e, r, Math.round(n / 2)), !0;
1647
+ if (t >= 9601 && t <= 9608) {
1648
+ const g = t - 9600, w = Math.round(n * g / 8);
1649
+ return this.ctx.fillRect(i, e + n - w, r, w), !0;
1650
+ }
1651
+ if (t >= 9609 && t <= 9615) {
1652
+ const g = 9616 - t, w = Math.round(r * g / 8);
1653
+ return this.ctx.fillRect(i, e, w, n), !0;
1654
+ }
1655
+ if (t === 9616) {
1656
+ const g = Math.round(r / 2);
1657
+ return this.ctx.fillRect(i + g, e, r - g, n), !0;
1658
+ }
1659
+ if (t >= 9617 && t <= 9619) {
1660
+ const g = [0.25, 0.5, 0.75][t - 9617], w = this.ctx.globalAlpha;
1661
+ return this.ctx.globalAlpha = w * g, this.ctx.fillRect(i, e, r, n), this.ctx.globalAlpha = w, !0;
1662
+ }
1663
+ if (t === 9620)
1664
+ return this.ctx.fillRect(i, e, r, Math.round(n / 8)), !0;
1665
+ if (t === 9621) {
1666
+ const g = Math.round(r * 7 / 8);
1667
+ return this.ctx.fillRect(i + g, e, r - g, n), !0;
1668
+ }
1669
+ const o = 8, a = 4, l = 2, h = 1, d = {
1670
+ 9622: l,
1671
+ 9623: h,
1672
+ 9624: o,
1673
+ 9625: o | l | h,
1674
+ 9626: o | h,
1675
+ 9627: o | a | l,
1676
+ 9628: o | a | h,
1677
+ 9629: a,
1678
+ 9630: a | l,
1679
+ 9631: a | l | h
1680
+ }[t];
1681
+ if (d === void 0)
1682
+ return !1;
1683
+ const p = Math.round(r / 2), m = Math.round(n / 2);
1684
+ return d & o && this.ctx.fillRect(i, e, p, m), d & a && this.ctx.fillRect(i + p, e, r - p, m), d & l && this.ctx.fillRect(i, e + m, p, n - m), d & h && this.ctx.fillRect(i + p, e + m, r - p, n - m), !0;
1685
+ }
1686
+ /**
1687
+ * Substitute a cell's text rendering with a slice of a kitty graphics
1688
+ * image. Called from renderCellText when the cell's codepoint is
1689
+ * U+10EEEE.
1690
+ *
1691
+ * Decodes the image_id from cell.fg_* (low 24 bits; high byte from
1692
+ * an optional third combining diacritic) and the row/col-of-image
1693
+ * from the first two combining diacritics on the cell. Looks up the
1694
+ * virtual placement (from precomputeKittyState) for grid dims, then
1695
+ * draws the matching slice scaled to one terminal cell.
1696
+ *
1697
+ * Returns true if the cell was handled as a placeholder; false to
1698
+ * fall through to normal text rendering (e.g., unknown image, no
1699
+ * matching virtual placement, or malformed diacritics).
1700
+ */
1701
+ renderPlaceholderCell(t, i, e) {
1702
+ var C;
1703
+ const s = this.currentRenderBuffer, r = this.currentKittyGraphics;
1704
+ if (!s || r === null || !s.getGrapheme)
1705
+ return !1;
1706
+ const n = s.getGrapheme(e, i);
1707
+ if (!n || n.length < 3)
1708
+ return !1;
1709
+ const o = H(n[1]), a = H(n[2]);
1710
+ if (o < 0 || a < 0)
1711
+ return !1;
1712
+ const l = t.fg_r << 16 | t.fg_g << 8 | t.fg_b;
1713
+ let h = l;
1714
+ if (n.length >= 4) {
1715
+ const M = H(n[3]);
1716
+ M >= 0 && (h = M << 24 | l);
1717
+ }
1718
+ const u = this.kittyVirtualPlacements.get(h);
1719
+ if (!u)
1720
+ return !1;
1721
+ const d = (C = s.getKittyImagePixels) == null ? void 0 : C.call(s, r, h);
1722
+ if (!d)
1723
+ return !1;
1724
+ const p = this.getOrDecodeKittyImage(s, r, h);
1725
+ if (!p)
1726
+ return !1;
1727
+ const m = d.width / u.gridCols, g = d.height / u.gridRows, w = a * m, S = o * g, b = i * this.metrics.width, f = e * this.metrics.height, v = this.ctx.imageSmoothingEnabled;
1728
+ return this.ctx.imageSmoothingEnabled = !1, this.ctx.drawImage(
1729
+ p,
1730
+ w,
1731
+ S,
1732
+ m,
1733
+ g,
1734
+ b,
1735
+ f,
1736
+ this.metrics.width,
1737
+ this.metrics.height
1738
+ ), this.ctx.imageSmoothingEnabled = v, !0;
1739
+ }
1740
+ renderKittyImages() {
1741
+ const t = this.currentRenderBuffer, i = this.currentKittyGraphics;
1742
+ if (!(!t || i === null || !t.getKittyImagePixels))
1743
+ for (const e of this.currentDirectPlacements) {
1744
+ let s = this.kittyImageCache.get(e.imageId);
1745
+ const r = t.getKittyImagePixels(i, e.imageId);
1746
+ if (r) {
1747
+ if (!s || !N(s, r)) {
1748
+ const n = this.decodeKittyImageToCanvas(r);
1749
+ if (!n)
1750
+ continue;
1751
+ s = {
1752
+ canvas: n,
1753
+ width: r.width,
1754
+ height: r.height,
1755
+ format: r.format,
1756
+ dataPtr: r.data.byteOffset,
1757
+ dataLen: r.data.length
1758
+ }, this.kittyImageCache.set(e.imageId, s);
1759
+ }
1760
+ this.ctx.drawImage(
1761
+ s.canvas,
1762
+ e.sourceX,
1763
+ e.sourceY,
1764
+ e.sourceWidth,
1765
+ e.sourceHeight,
1766
+ e.viewportCol * this.metrics.width,
1767
+ e.viewportRow * this.metrics.height,
1768
+ e.pixelWidth,
1769
+ e.pixelHeight
1770
+ );
1771
+ }
1772
+ }
1773
+ }
1774
+ /**
1775
+ * Decode a kitty graphics image into a canvas suitable for drawImage.
1776
+ * Expands non-RGBA formats into RGBA via putImageData; PNG payloads
1777
+ * (which require a JS-side decoder set up via ghostty_sys_set) are
1778
+ * not supported in this MVP and return null.
1779
+ */
1780
+ decodeKittyImageToCanvas(t) {
1781
+ const { width: i, height: e, format: s, data: r } = t;
1782
+ if (i === 0 || e === 0)
1783
+ return null;
1784
+ const n = new Uint8ClampedArray(new ArrayBuffer(i * e * 4));
1785
+ switch (s) {
1786
+ case D.RGBA:
1787
+ n.set(r);
1788
+ break;
1789
+ case D.RGB:
1790
+ for (let l = 0, h = 0; l < r.length; l += 3, h += 4)
1791
+ n[h] = r[l], n[h + 1] = r[l + 1], n[h + 2] = r[l + 2], n[h + 3] = 255;
1792
+ break;
1793
+ case D.GRAY:
1794
+ for (let l = 0, h = 0; l < r.length; l++, h += 4) {
1795
+ const u = r[l];
1796
+ n[h] = u, n[h + 1] = u, n[h + 2] = u, n[h + 3] = 255;
1797
+ }
1798
+ break;
1799
+ case D.GRAY_ALPHA:
1800
+ for (let l = 0, h = 0; l < r.length; l += 2, h += 4) {
1801
+ const u = r[l];
1802
+ n[h] = u, n[h + 1] = u, n[h + 2] = u, n[h + 3] = r[l + 1];
1803
+ }
1804
+ break;
1805
+ default:
1806
+ return null;
1807
+ }
1808
+ const o = document.createElement("canvas");
1809
+ o.width = i, o.height = e;
1810
+ const a = o.getContext("2d");
1811
+ return a ? (a.putImageData(new ImageData(n, i, e), 0, 0), o) : null;
1812
+ }
1813
+ /**
1814
+ * Render cursor
1815
+ */
1816
+ renderCursor(t, i, e) {
1817
+ var o;
1818
+ const s = t * this.metrics.width, r = i * this.metrics.height, n = e ?? this.cursorStyle;
1819
+ switch (this.ctx.fillStyle = this.theme.cursor, n) {
1820
+ case "block":
1821
+ this.ctx.fillRect(s, r, this.metrics.width, this.metrics.height);
1822
+ {
1823
+ const h = (o = this.currentBuffer) == null ? void 0 : o.getLine(i);
1824
+ h != null && h[t] && (this.ctx.save(), this.ctx.beginPath(), this.ctx.rect(s, r, this.metrics.width, this.metrics.height), this.ctx.clip(), this.renderCellText(h[t], t, i, this.theme.cursorAccent), this.ctx.restore());
1825
+ }
1826
+ break;
1827
+ case "underline":
1828
+ const a = Math.max(2, Math.floor(this.metrics.height * 0.15));
1829
+ this.ctx.fillRect(
1830
+ s,
1831
+ r + this.metrics.height - a,
1832
+ this.metrics.width,
1833
+ a
1834
+ );
1835
+ break;
1836
+ case "bar":
1837
+ const l = Math.max(2, Math.floor(this.metrics.width * 0.15));
1838
+ this.ctx.fillRect(s, r, l, this.metrics.height);
1839
+ break;
1840
+ }
1841
+ }
1842
+ // ==========================================================================
1843
+ // Cursor Blinking
1844
+ // ==========================================================================
1845
+ /**
1846
+ * Set a callback the renderer invokes when its internal state changes
1847
+ * outside the normal render-driven path (today: cursor-blink toggles).
1848
+ * Lets an event-driven Terminal wake its render scheduler instead of
1849
+ * polling every frame to catch the blink flip.
1850
+ */
1851
+ setOnRequestRender(t) {
1852
+ this.onRequestRender = t;
1853
+ }
1854
+ startCursorBlink() {
1855
+ this.cursorBlinkInterval = window.setInterval(() => {
1856
+ var t;
1857
+ this.cursorVisible = !this.cursorVisible, (t = this.onRequestRender) == null || t.call(this);
1858
+ }, 530);
1859
+ }
1860
+ stopCursorBlink() {
1861
+ this.cursorBlinkInterval !== void 0 && (clearInterval(this.cursorBlinkInterval), this.cursorBlinkInterval = void 0), this.cursorVisible = !0;
1862
+ }
1863
+ // ==========================================================================
1864
+ // Public API
1865
+ // ==========================================================================
1866
+ /**
1867
+ * Update theme colors
1868
+ */
1869
+ setTheme(t) {
1870
+ this.theme = { ...E, ...t }, this.palette = [
1871
+ this.theme.black,
1872
+ this.theme.red,
1873
+ this.theme.green,
1874
+ this.theme.yellow,
1875
+ this.theme.blue,
1876
+ this.theme.magenta,
1877
+ this.theme.cyan,
1878
+ this.theme.white,
1879
+ this.theme.brightBlack,
1880
+ this.theme.brightRed,
1881
+ this.theme.brightGreen,
1882
+ this.theme.brightYellow,
1883
+ this.theme.brightBlue,
1884
+ this.theme.brightMagenta,
1885
+ this.theme.brightCyan,
1886
+ this.theme.brightWhite
1887
+ ];
1888
+ }
1889
+ /**
1890
+ * Update font size
1891
+ */
1892
+ setFontSize(t) {
1893
+ this.fontSize = t, this.metrics = this.measureFont();
1894
+ }
1895
+ /**
1896
+ * Update font family
1897
+ */
1898
+ setFontFamily(t) {
1899
+ this.fontFamily = t, this.metrics = this.measureFont();
1900
+ }
1901
+ /**
1902
+ * Update cursor style
1903
+ */
1904
+ setCursorStyle(t) {
1905
+ this.cursorStyle = t;
1906
+ }
1907
+ /**
1908
+ * Enable/disable cursor blinking
1909
+ */
1910
+ setCursorBlink(t) {
1911
+ t && !this.cursorBlink ? (this.cursorBlink = !0, this.startCursorBlink()) : !t && this.cursorBlink && (this.cursorBlink = !1, this.stopCursorBlink());
1912
+ }
1913
+ /**
1914
+ * Get current font metrics
1915
+ */
1916
+ /**
1917
+ * Render scrollbar (Phase 2)
1918
+ * Shows scroll position and allows click/drag interaction
1919
+ * @param opacity Opacity level (0-1) for fade in/out effect
1920
+ */
1921
+ renderScrollbar(t, i, e, s = 1) {
1922
+ const r = this.ctx, n = this.canvas.height / this.devicePixelRatio, o = this.canvas.width / this.devicePixelRatio, a = 8, l = o - a - 4, h = 4, u = n - h * 2;
1923
+ if (r.clearRect(l - 2, 0, a + 6, n), r.fillStyle = this.theme.background, r.fillRect(l - 2, 0, a + 6, n), s <= 0 || i === 0)
1924
+ return;
1925
+ const d = i + e, p = Math.max(20, e / d * u), m = t / i, g = h + (u - p) * (1 - m);
1926
+ r.fillStyle = `rgba(128, 128, 128, ${0.1 * s})`, r.fillRect(l, h, a, u);
1927
+ const S = t > 0 ? 0.5 : 0.3;
1928
+ r.fillStyle = `rgba(128, 128, 128, ${S * s})`, r.fillRect(l, g, a, p);
1929
+ }
1930
+ getMetrics() {
1931
+ return { ...this.metrics };
1932
+ }
1933
+ /**
1934
+ * Get canvas element (needed by SelectionManager)
1935
+ */
1936
+ getCanvas() {
1937
+ return this.canvas;
1938
+ }
1939
+ /**
1940
+ * Set selection manager (for rendering selection)
1941
+ */
1942
+ setSelectionManager(t) {
1943
+ this.selectionManager = t;
1944
+ }
1945
+ /**
1946
+ * Check if a cell at (x, y) is within the current selection.
1947
+ * Uses cached selection coordinates for performance.
1948
+ */
1949
+ isInSelection(t, i) {
1950
+ const e = this.currentSelectionCoords;
1951
+ if (!e)
1952
+ return !1;
1953
+ const { startCol: s, startRow: r, endCol: n, endRow: o } = e;
1954
+ return r === o ? i === r && t >= s && t <= n : i === r ? t >= s : i === o ? t <= n : i > r && i < o;
1955
+ }
1956
+ /**
1957
+ * Set the currently hovered hyperlink ID for rendering underlines
1958
+ */
1959
+ setHoveredHyperlinkId(t) {
1960
+ var i;
1961
+ this.hoveredHyperlinkId !== t && (this.hoveredHyperlinkId = t, (i = this.onRequestRender) == null || i.call(this));
1962
+ }
1963
+ /**
1964
+ * Set the currently hovered link range for rendering underlines (for regex-detected URLs)
1965
+ * Pass null to clear the hover state
1966
+ */
1967
+ setHoveredLinkRange(t) {
1968
+ var i;
1969
+ this.hoveredLinkRange !== t && (this.hoveredLinkRange = t, (i = this.onRequestRender) == null || i.call(this));
1970
+ }
1971
+ /**
1972
+ * Get character cell width (for coordinate conversion)
1973
+ */
1974
+ get charWidth() {
1975
+ return this.metrics.width;
1976
+ }
1977
+ /**
1978
+ * Get character cell height (for coordinate conversion)
1979
+ */
1980
+ get charHeight() {
1981
+ return this.metrics.height;
1982
+ }
1983
+ /**
1984
+ * Clear entire canvas
1985
+ */
1986
+ clear() {
1987
+ this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height), this.ctx.fillStyle = this.theme.background, this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
1988
+ }
1989
+ /**
1990
+ * Cleanup resources
1991
+ */
1992
+ dispose() {
1993
+ this.stopCursorBlink();
1994
+ }
1995
+ }
1996
+ const _ = class I {
1997
+ // ms between scroll steps
1998
+ constructor(t, i, e, s) {
1999
+ this.selectionStart = null, this.selectionEnd = null, this.isSelecting = !1, this.mouseDownX = 0, this.mouseDownY = 0, this.dragThresholdMet = !1, this.mouseDownTarget = null, this.dirtySelectionRows = /* @__PURE__ */ new Set(), this.selectionChangedEmitter = new k(), this.boundMouseUpHandler = null, this.boundContextMenuHandler = null, this.boundClickHandler = null, this.boundDocumentMouseMoveHandler = null, this.autoScrollInterval = null, this.autoScrollDirection = 0, this.terminal = t, this.renderer = i, this.wasmTerm = e, this.textarea = s, this.attachEventListeners();
2000
+ }
2001
+ // pixels from edge to trigger scroll
2002
+ /**
2003
+ * Get current viewport Y position (how many lines scrolled into history)
2004
+ */
2005
+ getViewportY() {
2006
+ const t = typeof this.terminal.getViewportY == "function" ? this.terminal.getViewportY() : this.terminal.viewportY || 0;
2007
+ return Math.max(0, Math.floor(t));
2008
+ }
2009
+ /**
2010
+ * Convert viewport row to absolute buffer row
2011
+ * Absolute row is an index into combined buffer: scrollback (0 to len-1) + screen (len to len+rows-1)
2012
+ */
2013
+ viewportRowToAbsolute(t) {
2014
+ const i = this.wasmTerm.getScrollbackLength(), e = this.getViewportY();
2015
+ return i + t - e;
2016
+ }
2017
+ /**
2018
+ * Convert absolute buffer row to viewport row (may be outside visible range)
2019
+ */
2020
+ absoluteRowToViewport(t) {
2021
+ const i = this.wasmTerm.getScrollbackLength(), e = this.getViewportY();
2022
+ return t - i + e;
2023
+ }
2024
+ // ==========================================================================
2025
+ // Public API
2026
+ // ==========================================================================
2027
+ /**
2028
+ * Get the selected text as a string
2029
+ */
2030
+ getSelection() {
2031
+ if (!this.selectionStart || !this.selectionEnd)
2032
+ return "";
2033
+ let { col: t, absoluteRow: i } = this.selectionStart, { col: e, absoluteRow: s } = this.selectionEnd;
2034
+ (i > s || i === s && t > e) && ([t, e] = [e, t], [i, s] = [s, i]);
2035
+ const r = this.wasmTerm.getScrollbackLength();
2036
+ let n = "";
2037
+ for (let o = i; o <= s; o++) {
2038
+ let a = null;
2039
+ if (o < r)
2040
+ a = this.wasmTerm.getScrollbackLine(o);
2041
+ else {
2042
+ const p = o - r;
2043
+ a = this.wasmTerm.getLine(p);
2044
+ }
2045
+ if (!a)
2046
+ continue;
2047
+ let l = -1;
2048
+ const h = o === i ? t : 0, u = o === s ? e : a.length - 1;
2049
+ let d = "";
2050
+ for (let p = h; p <= u; p++) {
2051
+ const m = a[p];
2052
+ if (m && m.codepoint !== 0) {
2053
+ let g;
2054
+ if (m.grapheme_len > 0)
2055
+ if (o < r)
2056
+ g = this.wasmTerm.getScrollbackGraphemeString(o, p);
2057
+ else {
2058
+ const w = o - r;
2059
+ g = this.wasmTerm.getGraphemeString(w, p);
2060
+ }
2061
+ else
2062
+ g = String.fromCodePoint(m.codepoint);
2063
+ d += g, g.trim() && (l = d.length);
2064
+ } else
2065
+ (!m || m.width !== 0) && (d += " ");
2066
+ }
2067
+ l >= 0 ? d = d.substring(0, l) : d = "", n += d, o < s && (n += `
2068
+ `);
2069
+ }
2070
+ return n;
2071
+ }
2072
+ /**
2073
+ * Check if there's an active selection
2074
+ */
2075
+ hasSelection() {
2076
+ return !(!this.selectionStart || !this.selectionEnd || this.isSelecting && !this.dragThresholdMet);
2077
+ }
2078
+ /**
2079
+ * Copy the current selection to clipboard
2080
+ * @returns true if there was text to copy, false otherwise
2081
+ */
2082
+ copySelection() {
2083
+ if (!this.hasSelection())
2084
+ return !1;
2085
+ const t = this.getSelection();
2086
+ return t ? (this.copyToClipboard(t), !0) : !1;
2087
+ }
2088
+ /**
2089
+ * Clear the selection
2090
+ */
2091
+ clearSelection() {
2092
+ if (!this.hasSelection())
2093
+ return;
2094
+ const t = this.normalizeSelection();
2095
+ if (t)
2096
+ for (let i = t.startRow; i <= t.endRow; i++)
2097
+ this.dirtySelectionRows.add(i);
2098
+ this.selectionStart = null, this.selectionEnd = null, this.isSelecting = !1, this.requestRender();
2099
+ }
2100
+ /**
2101
+ * Select all text in the terminal
2102
+ */
2103
+ selectAll() {
2104
+ const t = this.wasmTerm.getDimensions(), i = this.getViewportY();
2105
+ this.selectionStart = { col: 0, absoluteRow: i }, this.selectionEnd = { col: t.cols - 1, absoluteRow: i + t.rows - 1 }, this.requestRender(), this.selectionChangedEmitter.fire();
2106
+ }
2107
+ /**
2108
+ * Select text at specific column and row with length
2109
+ * xterm.js compatible API
2110
+ */
2111
+ select(t, i, e) {
2112
+ const s = this.wasmTerm.getDimensions();
2113
+ i = Math.max(0, Math.min(i, s.rows - 1)), t = Math.max(0, Math.min(t, s.cols - 1));
2114
+ let r = i, n = t + e - 1;
2115
+ for (; n >= s.cols; )
2116
+ n -= s.cols, r++;
2117
+ r = Math.min(r, s.rows - 1);
2118
+ const o = this.getViewportY();
2119
+ this.selectionStart = { col: t, absoluteRow: o + i }, this.selectionEnd = { col: n, absoluteRow: o + r }, this.requestRender(), this.selectionChangedEmitter.fire();
2120
+ }
2121
+ /**
2122
+ * Select entire lines from start to end
2123
+ * xterm.js compatible API
2124
+ */
2125
+ selectLines(t, i) {
2126
+ const e = this.wasmTerm.getDimensions();
2127
+ t = Math.max(0, Math.min(t, e.rows - 1)), i = Math.max(0, Math.min(i, e.rows - 1)), t > i && ([t, i] = [i, t]), this.selectionStart = { col: 0, absoluteRow: this.viewportRowToAbsolute(t) }, this.selectionEnd = { col: e.cols - 1, absoluteRow: this.viewportRowToAbsolute(i) }, this.requestRender(), this.selectionChangedEmitter.fire();
2128
+ }
2129
+ /**
2130
+ * Get selection position as buffer range
2131
+ * xterm.js compatible API
2132
+ */
2133
+ getSelectionPosition() {
2134
+ const t = this.normalizeSelection();
2135
+ if (t)
2136
+ return {
2137
+ start: { x: t.startCol, y: t.startRow },
2138
+ end: { x: t.endCol, y: t.endRow }
2139
+ };
2140
+ }
2141
+ /**
2142
+ * Deselect all text
2143
+ * xterm.js compatible API
2144
+ */
2145
+ deselect() {
2146
+ this.clearSelection(), this.selectionChangedEmitter.fire();
2147
+ }
2148
+ /**
2149
+ * Focus the terminal (make it receive keyboard input)
2150
+ */
2151
+ focus() {
2152
+ const t = this.renderer.getCanvas();
2153
+ t.parentElement && t.parentElement.focus();
2154
+ }
2155
+ /**
2156
+ * Get current selection coordinates (for rendering)
2157
+ */
2158
+ getSelectionCoords() {
2159
+ return this.normalizeSelection();
2160
+ }
2161
+ /**
2162
+ * Get dirty selection rows that need redraw (for clearing old highlight)
2163
+ */
2164
+ getDirtySelectionRows() {
2165
+ return this.dirtySelectionRows;
2166
+ }
2167
+ /**
2168
+ * Clear the dirty selection rows tracking (after redraw)
2169
+ */
2170
+ clearDirtySelectionRows() {
2171
+ this.dirtySelectionRows.clear();
2172
+ }
2173
+ /**
2174
+ * Get selection change event accessor
2175
+ */
2176
+ get onSelectionChange() {
2177
+ return this.selectionChangedEmitter.event;
2178
+ }
2179
+ /**
2180
+ * Cleanup resources
2181
+ */
2182
+ dispose() {
2183
+ this.selectionChangedEmitter.dispose(), this.stopAutoScroll(), this.boundMouseUpHandler && (document.removeEventListener("mouseup", this.boundMouseUpHandler), this.boundMouseUpHandler = null), this.boundDocumentMouseMoveHandler && (document.removeEventListener("mousemove", this.boundDocumentMouseMoveHandler), this.boundDocumentMouseMoveHandler = null), this.boundContextMenuHandler && (this.renderer.getCanvas().removeEventListener("contextmenu", this.boundContextMenuHandler), this.boundContextMenuHandler = null), this.boundClickHandler && (document.removeEventListener("click", this.boundClickHandler), this.boundClickHandler = null);
2184
+ }
2185
+ // ==========================================================================
2186
+ // Private Methods
2187
+ // ==========================================================================
2188
+ /**
2189
+ * Attach mouse event listeners to canvas
2190
+ */
2191
+ attachEventListeners() {
2192
+ const t = this.renderer.getCanvas();
2193
+ t.addEventListener("mousedown", (i) => {
2194
+ if (i.button === 0) {
2195
+ t.parentElement && t.parentElement.focus();
2196
+ const e = this.pixelToCell(i.offsetX, i.offsetY);
2197
+ this.hasSelection() && this.clearSelection();
2198
+ const r = this.viewportRowToAbsolute(e.row);
2199
+ this.selectionStart = { col: e.col, absoluteRow: r }, this.selectionEnd = { col: e.col, absoluteRow: r }, this.isSelecting = !0, this.mouseDownX = i.offsetX, this.mouseDownY = i.offsetY, this.dragThresholdMet = !1;
2200
+ }
2201
+ }), t.addEventListener("mousemove", (i) => {
2202
+ if (this.isSelecting) {
2203
+ if (!this.dragThresholdMet) {
2204
+ const r = i.offsetX - this.mouseDownX, n = i.offsetY - this.mouseDownY, o = this.renderer.getMetrics().width * 0.5;
2205
+ if (r * r + n * n < o * o)
2206
+ return;
2207
+ this.dragThresholdMet = !0;
2208
+ }
2209
+ this.markCurrentSelectionDirty();
2210
+ const e = this.pixelToCell(i.offsetX, i.offsetY), s = this.viewportRowToAbsolute(e.row);
2211
+ this.selectionEnd = { col: e.col, absoluteRow: s }, this.requestRender(), this.updateAutoScroll(i.offsetY, t.clientHeight);
2212
+ }
2213
+ }), t.addEventListener("mouseleave", (i) => {
2214
+ if (this.isSelecting) {
2215
+ const e = t.getBoundingClientRect();
2216
+ i.clientY < e.top ? this.startAutoScroll(-1) : i.clientY > e.bottom && this.startAutoScroll(1);
2217
+ }
2218
+ }), t.addEventListener("mouseenter", () => {
2219
+ this.isSelecting && this.stopAutoScroll();
2220
+ }), this.boundDocumentMouseMoveHandler = (i) => {
2221
+ if (this.isSelecting) {
2222
+ if (!this.dragThresholdMet) {
2223
+ const a = i.clientX - (t.getBoundingClientRect().left + this.mouseDownX), l = i.clientY - (t.getBoundingClientRect().top + this.mouseDownY), h = this.renderer.getMetrics().width * 0.5;
2224
+ if (a * a + l * l < h * h)
2225
+ return;
2226
+ this.dragThresholdMet = !0;
2227
+ }
2228
+ const e = t.getBoundingClientRect(), s = Math.max(e.left, Math.min(i.clientX, e.right)), r = Math.max(e.top, Math.min(i.clientY, e.bottom)), n = s - e.left, o = r - e.top;
2229
+ if ((i.clientX < e.left || i.clientX > e.right || i.clientY < e.top || i.clientY > e.bottom) && (i.clientY < e.top ? this.startAutoScroll(-1) : i.clientY > e.bottom ? this.startAutoScroll(1) : this.stopAutoScroll(), this.autoScrollDirection === 0)) {
2230
+ this.markCurrentSelectionDirty();
2231
+ const a = this.pixelToCell(n, o), l = this.viewportRowToAbsolute(a.row);
2232
+ this.selectionEnd = { col: a.col, absoluteRow: l }, this.requestRender();
2233
+ }
2234
+ }
2235
+ }, document.addEventListener("mousemove", this.boundDocumentMouseMoveHandler), document.addEventListener("mousedown", (i) => {
2236
+ this.mouseDownTarget = i.target;
2237
+ }), this.boundMouseUpHandler = (i) => {
2238
+ if (this.isSelecting) {
2239
+ if (this.isSelecting = !1, this.stopAutoScroll(), !this.dragThresholdMet) {
2240
+ this.clearSelection();
2241
+ return;
2242
+ }
2243
+ if (this.hasSelection()) {
2244
+ try {
2245
+ const e = this.getSelection();
2246
+ e && this.copyToClipboard(e);
2247
+ } catch {
2248
+ }
2249
+ this.selectionChangedEmitter.fire();
2250
+ }
2251
+ }
2252
+ }, document.addEventListener("mouseup", this.boundMouseUpHandler), t.addEventListener("click", (i) => {
2253
+ if (i.detail === 2) {
2254
+ const e = this.pixelToCell(i.offsetX, i.offsetY), s = this.getWordAtCell(e.col, e.row);
2255
+ if (s) {
2256
+ const r = this.viewportRowToAbsolute(e.row);
2257
+ this.selectionStart = { col: s.startCol, absoluteRow: r }, this.selectionEnd = { col: s.endCol, absoluteRow: r }, this.requestRender();
2258
+ try {
2259
+ const n = this.getSelection();
2260
+ n && this.copyToClipboard(n);
2261
+ } catch {
2262
+ }
2263
+ this.selectionChangedEmitter.fire();
2264
+ }
2265
+ } else if (i.detail >= 3) {
2266
+ const e = this.pixelToCell(i.offsetX, i.offsetY), s = this.viewportRowToAbsolute(e.row), r = this.wasmTerm.getScrollbackLength();
2267
+ let n = null;
2268
+ if (s < r)
2269
+ n = this.wasmTerm.getScrollbackLine(s);
2270
+ else {
2271
+ const a = s - r;
2272
+ n = this.wasmTerm.getLine(a);
2273
+ }
2274
+ let o = -1;
2275
+ if (n) {
2276
+ for (let a = n.length - 1; a >= 0; a--)
2277
+ if (n[a] && n[a].codepoint !== 0 && n[a].codepoint !== 32) {
2278
+ o = a;
2279
+ break;
2280
+ }
2281
+ }
2282
+ if (o >= 0) {
2283
+ this.selectionStart = { col: 0, absoluteRow: s }, this.selectionEnd = { col: o, absoluteRow: s }, this.requestRender();
2284
+ try {
2285
+ const a = this.getSelection();
2286
+ a && this.copyToClipboard(a);
2287
+ } catch {
2288
+ }
2289
+ this.selectionChangedEmitter.fire();
2290
+ }
2291
+ }
2292
+ }), this.boundContextMenuHandler = (i) => {
2293
+ if (this.renderer.getCanvas().getBoundingClientRect(), this.textarea.style.position = "fixed", this.textarea.style.left = `${i.clientX}px`, this.textarea.style.top = `${i.clientY}px`, this.textarea.style.width = "1px", this.textarea.style.height = "1px", this.textarea.style.zIndex = "1000", this.textarea.style.opacity = "0", this.textarea.style.pointerEvents = "auto", this.hasSelection()) {
2294
+ const s = this.getSelection();
2295
+ this.textarea.value = s, this.textarea.select(), this.textarea.setSelectionRange(0, s.length);
2296
+ } else
2297
+ this.textarea.value = "";
2298
+ this.textarea.focus(), setTimeout(() => {
2299
+ const s = () => {
2300
+ this.textarea.style.pointerEvents = "none", this.textarea.style.zIndex = "-10", this.textarea.style.width = "0", this.textarea.style.height = "0", this.textarea.style.left = "0", this.textarea.style.top = "0", this.textarea.value = "", document.removeEventListener("click", s), document.removeEventListener("contextmenu", s), this.textarea.removeEventListener("blur", s);
2301
+ };
2302
+ document.addEventListener("click", s, { once: !0 }), document.addEventListener("contextmenu", s, { once: !0 }), this.textarea.addEventListener("blur", s, { once: !0 });
2303
+ }, 10);
2304
+ }, t.addEventListener("contextmenu", this.boundContextMenuHandler), this.boundClickHandler = (i) => {
2305
+ if (this.isSelecting || this.mouseDownTarget && t.contains(this.mouseDownTarget))
2306
+ return;
2307
+ const s = i.target;
2308
+ t.contains(s) || this.hasSelection() && this.clearSelection();
2309
+ }, document.addEventListener("click", this.boundClickHandler);
2310
+ }
2311
+ /**
2312
+ * Mark current selection rows as dirty for redraw
2313
+ */
2314
+ markCurrentSelectionDirty() {
2315
+ const t = this.normalizeSelection();
2316
+ if (t)
2317
+ for (let i = t.startRow; i <= t.endRow; i++)
2318
+ this.dirtySelectionRows.add(i);
2319
+ }
2320
+ /**
2321
+ * Update auto-scroll based on mouse Y position within canvas
2322
+ */
2323
+ updateAutoScroll(t, i) {
2324
+ const e = I.AUTO_SCROLL_EDGE_SIZE;
2325
+ t < e ? this.startAutoScroll(-1) : t > i - e ? this.startAutoScroll(1) : this.stopAutoScroll();
2326
+ }
2327
+ /**
2328
+ * Start auto-scrolling in the given direction
2329
+ */
2330
+ startAutoScroll(t) {
2331
+ this.autoScrollInterval !== null && this.autoScrollDirection === t || (this.stopAutoScroll(), this.autoScrollDirection = t, this.autoScrollInterval = setInterval(() => {
2332
+ if (!this.isSelecting) {
2333
+ this.stopAutoScroll();
2334
+ return;
2335
+ }
2336
+ const i = I.AUTO_SCROLL_SPEED * this.autoScrollDirection;
2337
+ if (this.terminal.scrollLines(i), this.selectionEnd) {
2338
+ const e = this.wasmTerm.getDimensions();
2339
+ if (this.autoScrollDirection < 0) {
2340
+ const s = this.viewportRowToAbsolute(0);
2341
+ s < this.selectionEnd.absoluteRow && (this.selectionEnd = { col: 0, absoluteRow: s });
2342
+ } else {
2343
+ const s = this.viewportRowToAbsolute(e.rows - 1);
2344
+ s > this.selectionEnd.absoluteRow && (this.selectionEnd = { col: e.cols - 1, absoluteRow: s });
2345
+ }
2346
+ }
2347
+ this.requestRender();
2348
+ }, I.AUTO_SCROLL_INTERVAL));
2349
+ }
2350
+ /**
2351
+ * Stop auto-scrolling
2352
+ */
2353
+ stopAutoScroll() {
2354
+ this.autoScrollInterval !== null && (clearInterval(this.autoScrollInterval), this.autoScrollInterval = null), this.autoScrollDirection = 0;
2355
+ }
2356
+ /**
2357
+ * Convert pixel coordinates to terminal cell coordinates
2358
+ */
2359
+ pixelToCell(t, i) {
2360
+ const e = this.renderer.getMetrics(), s = Math.floor(t / e.width), r = Math.floor(i / e.height);
2361
+ return {
2362
+ col: Math.max(0, Math.min(s, this.terminal.cols - 1)),
2363
+ row: Math.max(0, Math.min(r, this.terminal.rows - 1))
2364
+ };
2365
+ }
2366
+ /**
2367
+ * Normalize selection coordinates (handle backward selection)
2368
+ * Returns coordinates in VIEWPORT space for rendering, clamped to visible area
2369
+ */
2370
+ normalizeSelection() {
2371
+ if (!this.selectionStart || !this.selectionEnd)
2372
+ return null;
2373
+ let { col: t, absoluteRow: i } = this.selectionStart, { col: e, absoluteRow: s } = this.selectionEnd;
2374
+ (i > s || i === s && t > e) && ([t, e] = [e, t], [i, s] = [s, i]);
2375
+ let r = this.absoluteRowToViewport(i), n = this.absoluteRowToViewport(s);
2376
+ const o = this.wasmTerm.getDimensions(), a = o.rows - 1;
2377
+ return n < 0 || r > a ? null : (r < 0 && (r = 0, t = 0), n > a && (n = a, e = o.cols - 1), { startCol: t, startRow: r, endCol: e, endRow: n });
2378
+ }
2379
+ /**
2380
+ * Get word boundaries at a cell position
2381
+ */
2382
+ getWordAtCell(t, i) {
2383
+ const e = this.viewportRowToAbsolute(i), s = this.wasmTerm.getScrollbackLength();
2384
+ let r;
2385
+ try {
2386
+ if (e < s)
2387
+ r = this.wasmTerm.getScrollbackLine(e);
2388
+ else {
2389
+ const l = e - s;
2390
+ r = this.wasmTerm.getLine(l);
2391
+ }
2392
+ } catch {
2393
+ return null;
2394
+ }
2395
+ if (!r)
2396
+ return null;
2397
+ const n = (l) => {
2398
+ if (!l || l.codepoint === 0)
2399
+ return !1;
2400
+ const h = String.fromCodePoint(l.codepoint);
2401
+ return /[\w\-./~@+]/.test(h);
2402
+ };
2403
+ if (!n(r[t]))
2404
+ return null;
2405
+ let o = t;
2406
+ for (; o > 0 && n(r[o - 1]); )
2407
+ o--;
2408
+ let a = t;
2409
+ for (; a < r.length - 1 && n(r[a + 1]); )
2410
+ a++;
2411
+ return { startCol: o, endCol: a };
2412
+ }
2413
+ /**
2414
+ * Copy text to clipboard
2415
+ *
2416
+ * Strategy (modern APIs first):
2417
+ * 1. Try ClipboardItem API (works in Safari and modern browsers)
2418
+ * - Safari requires the ClipboardItem to be created synchronously within user gesture
2419
+ * 2. Try navigator.clipboard.writeText (modern async API, may fail in Safari)
2420
+ * 3. Fall back to execCommand (legacy, for older browsers)
2421
+ */
2422
+ copyToClipboard(t) {
2423
+ if (navigator.clipboard && typeof ClipboardItem < "u")
2424
+ try {
2425
+ const i = new Blob([t], { type: "text/plain" }), e = new ClipboardItem({
2426
+ "text/plain": i
2427
+ });
2428
+ navigator.clipboard.write([e]).catch((s) => {
2429
+ console.warn("ClipboardItem write failed, trying writeText:", s), this.copyWithWriteText(t);
2430
+ });
2431
+ return;
2432
+ } catch {
2433
+ }
2434
+ if (navigator.clipboard && navigator.clipboard.writeText) {
2435
+ navigator.clipboard.writeText(t).catch((i) => {
2436
+ console.warn("Clipboard writeText failed, trying execCommand:", i), this.copyWithExecCommand(t);
2437
+ });
2438
+ return;
2439
+ }
2440
+ this.copyWithExecCommand(t);
2441
+ }
2442
+ /**
2443
+ * Copy using navigator.clipboard.writeText
2444
+ */
2445
+ copyWithWriteText(t) {
2446
+ navigator.clipboard && navigator.clipboard.writeText ? navigator.clipboard.writeText(t).catch((i) => {
2447
+ console.warn("Clipboard writeText failed, trying execCommand:", i), this.copyWithExecCommand(t);
2448
+ }) : this.copyWithExecCommand(t);
2449
+ }
2450
+ /**
2451
+ * Copy using legacy execCommand (fallback for older browsers)
2452
+ */
2453
+ copyWithExecCommand(t) {
2454
+ const i = document.activeElement;
2455
+ try {
2456
+ const e = this.textarea;
2457
+ e.value = t, e.style.position = "fixed", e.style.left = "-9999px", e.style.top = "0", e.style.width = "1px", e.style.height = "1px", e.style.opacity = "0", e.focus(), e.select(), e.setSelectionRange(0, t.length);
2458
+ const s = document.execCommand("copy");
2459
+ i && i.focus(), s || console.warn("execCommand copy failed");
2460
+ } catch (e) {
2461
+ console.warn("execCommand copy threw:", e), i && i.focus();
2462
+ }
2463
+ }
2464
+ /**
2465
+ * Request a render update (triggers selection overlay redraw)
2466
+ */
2467
+ requestRender() {
2468
+ }
2469
+ };
2470
+ _.AUTO_SCROLL_EDGE_SIZE = 30;
2471
+ _.AUTO_SCROLL_SPEED = 3;
2472
+ _.AUTO_SCROLL_INTERVAL = 50;
2473
+ let st = _;
2474
+ function U(y, t) {
2475
+ const i = String(y || "").trim();
2476
+ if (!i)
2477
+ return t;
2478
+ if (i.startsWith("#")) {
2479
+ const s = i.slice(1), r = s.length === 3 ? s.split("").map((n) => n + n).join("") : s;
2480
+ if (/^[0-9a-fA-F]{6}$/.test(r)) {
2481
+ const n = Number.parseInt(r, 16);
2482
+ return { r: n >> 16 & 255, g: n >> 8 & 255, b: n & 255 };
2483
+ }
2484
+ }
2485
+ const e = i.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/i);
2486
+ return e ? {
2487
+ r: Number.parseInt(e[1], 10),
2488
+ g: Number.parseInt(e[2], 10),
2489
+ b: Number.parseInt(e[3], 10)
2490
+ } : t;
2491
+ }
2492
+ function rt(y, t, i) {
2493
+ const e = U(i.foreground, { r: 212, g: 212, b: 212 }), s = U(i.background, { r: 30, g: 30, b: 30 }), r = {
2494
+ codepoint: 32,
2495
+ fg_r: e.r,
2496
+ fg_g: e.g,
2497
+ fg_b: e.b,
2498
+ bg_r: s.r,
2499
+ bg_g: s.g,
2500
+ bg_b: s.b,
2501
+ fgIsDefault: !1,
2502
+ bgIsDefault: !1,
2503
+ flags: 0,
2504
+ width: 1,
2505
+ hyperlink_id: 0,
2506
+ grapheme_len: 0
2507
+ };
2508
+ return Array.from({ length: t }, () => Array.from({ length: y }, () => ({ ...r })));
2509
+ }
2510
+ const W = class z extends G {
2511
+ constructor(t = {}) {
2512
+ const i = t.ghostty ?? lt();
2513
+ super(i, t), this.unicode = {
2514
+ get activeVersion() {
2515
+ return "15.1";
2516
+ }
2517
+ }, this.selectionChangeEmitter = new k(), this.keyEmitter = new k(), this.renderEmitter = new k(), this.mouseCursorChangeEmitter = new k(), this.onSelectionChange = this.selectionChangeEmitter.event, this.onKey = this.keyEmitter.event, this.onRender = this.renderEmitter.event, this.onMouseCursorChange = this.mouseCursorChangeEmitter.event, this.isOpen = !1, this.writeQueue = [], this.awaitingEcho = !1, this.syncOutputStartTime = void 0, this.currentTheme = { ...E }, this.targetViewportY = 0, this.isDraggingScrollbar = !1, this.scrollbarDragStart = null, this.scrollbarDragStartViewportY = 0, this.scrollbarVisible = !1, this.scrollbarOpacity = 0, this.SCROLLBAR_HIDE_DELAY_MS = 1500, this.SCROLLBAR_FADE_DURATION_MS = 200, this.bootstrapCells = null, this.bootstrapDirty = !1, this.renderTick = () => {
2518
+ if (this.animationFrameId = void 0, this.isDisposed || !this.isOpen)
2519
+ return;
2520
+ if (this.wasmTerm.getMode(2026, !1)) {
2521
+ const s = performance.now();
2522
+ if (this.syncOutputStartTime === void 0 && (this.syncOutputStartTime = s), s - this.syncOutputStartTime < z.SYNC_OUTPUT_TIMEOUT_MS) {
2523
+ this.requestRender();
2524
+ return;
2525
+ }
2526
+ }
2527
+ this.syncOutputStartTime = void 0, this.renderer.render(this.wasmTerm, !1, this.viewportY, this, this.scrollbarOpacity), this.renderEmitter.fire({ start: 0, end: this.rows - 1 });
2528
+ const e = this.wasmTerm.getCursor();
2529
+ (e.x !== this.lastCursorX || e.y !== this.lastCursorY) && (this.lastCursorX = e.x, this.lastCursorY = e.y, this.cursorMoveEmitter.fire()), this.syncTextareaToCursor(e.x, e.y);
2530
+ }, this.lastOsc22Cursor = "", this.animateScroll = () => {
2531
+ if (!this.wasmTerm || this.scrollAnimationStartTime === void 0)
2532
+ return;
2533
+ const e = this.options.smoothScrollDuration ?? 100, s = this.targetViewportY - this.viewportY;
2534
+ if (Math.abs(s) < 0.01) {
2535
+ this.viewportY = this.targetViewportY, this.scrollEmitter.fire(Math.floor(this.viewportY)), this.getScrollbackLength() > 0 && this.showScrollbar(), this.scrollAnimationFrame = void 0, this.scrollAnimationStartTime = void 0, this.scrollAnimationStartY = void 0, this.requestRender();
2536
+ return;
2537
+ }
2538
+ const o = 1 - (1 / (e / 1e3 * 60)) ** 2;
2539
+ this.viewportY += s * o;
2540
+ const a = Math.floor(this.viewportY);
2541
+ this.scrollEmitter.fire(a), this.getScrollbackLength() > 0 && this.showScrollbar(), this.requestRender(), this.scrollAnimationFrame = requestAnimationFrame(this.animateScroll);
2542
+ }, this.handleMouseMove = (e) => {
2543
+ if (!(!this.canvas || !this.renderer || !this.wasmTerm)) {
2544
+ if (this.isDraggingScrollbar) {
2545
+ this.processScrollbarDrag(e);
2546
+ return;
2547
+ }
2548
+ if (this.linkDetector) {
2549
+ if (this.mouseMoveThrottleTimeout) {
2550
+ this.pendingMouseMove = e;
2551
+ return;
2552
+ }
2553
+ this.processMouseMove(e), this.mouseMoveThrottleTimeout = window.setTimeout(() => {
2554
+ if (this.mouseMoveThrottleTimeout = void 0, this.pendingMouseMove) {
2555
+ const s = this.pendingMouseMove;
2556
+ this.pendingMouseMove = void 0, this.processMouseMove(s);
2557
+ }
2558
+ }, 16);
2559
+ }
2560
+ }
2561
+ }, this.handleMouseLeave = () => {
2562
+ var e, s;
2563
+ this.renderer && this.wasmTerm && ((this.renderer.hoveredHyperlinkId || 0) > 0 && this.renderer.setHoveredHyperlinkId(0), this.renderer.setHoveredLinkRange(null)), this.currentHoveredLink && ((s = (e = this.currentHoveredLink).hover) == null || s.call(e, !1), this.currentHoveredLink = void 0, this.element && (this.element.style.cursor = "text", this.canvas && (this.canvas.style.cursor = "text")));
2564
+ }, this.handleClick = async (e) => {
2565
+ if (!this.canvas || !this.renderer || !this.linkDetector || !this.wasmTerm)
2566
+ return;
2567
+ const s = this.canvas.getBoundingClientRect(), r = Math.floor((e.clientX - s.left) / this.renderer.charWidth), o = Math.floor((e.clientY - s.top) / this.renderer.charHeight), a = this.wasmTerm.getScrollbackLength();
2568
+ let l;
2569
+ const h = this.getViewportY(), u = Math.max(0, Math.floor(h));
2570
+ if (u > 0)
2571
+ if (o < u)
2572
+ l = a - u + o;
2573
+ else {
2574
+ const p = o - u;
2575
+ l = a + p;
2576
+ }
2577
+ else
2578
+ l = a + o;
2579
+ const d = await this.linkDetector.getLinkAt(r, l);
2580
+ d && (d.activate(e), (e.ctrlKey || e.metaKey) && e.preventDefault());
2581
+ }, this.handleWheel = (e) => {
2582
+ var r, n, o, a, l, h, u;
2583
+ if (e.preventDefault(), e.stopPropagation(), this.customWheelEventHandler && this.customWheelEventHandler(e))
2584
+ return;
2585
+ if ((r = this.wasmTerm) != null && r.hasMouseTracking()) {
2586
+ (n = this.inputHandler) == null || n.handleWheelEvent(e);
2587
+ return;
2588
+ }
2589
+ if (((o = this.wasmTerm) == null ? void 0 : o.isAlternateScreen()) ?? !1) {
2590
+ if ((a = this.wasmTerm) != null && a.hasMouseTracking()) {
2591
+ const m = (l = this.renderer) == null ? void 0 : l.getMetrics(), g = this.canvas;
2592
+ if (m && g) {
2593
+ const w = g.getBoundingClientRect(), S = Math.max(1, Math.floor((e.clientX - w.left) / m.width) + 1), b = Math.max(1, Math.floor((e.clientY - w.top) / m.height) + 1), f = e.deltaY < 0 ? 64 : 65;
2594
+ this.dataEmitter.fire(`\x1B[<${f};${S};${b}M`);
2595
+ }
2596
+ return;
2597
+ }
2598
+ const d = e.deltaY > 0 ? "down" : "up", p = Math.min(Math.abs(Math.round(e.deltaY / 33)), 5);
2599
+ for (let m = 0; m < p; m++)
2600
+ this.dataEmitter.fire(d === "up" ? "\x1B[A" : "\x1B[B");
2601
+ } else {
2602
+ let d;
2603
+ if (e.deltaMode === WheelEvent.DOM_DELTA_PIXEL) {
2604
+ const p = ((u = (h = this.renderer) == null ? void 0 : h.getMetrics()) == null ? void 0 : u.height) ?? 20;
2605
+ d = e.deltaY / p;
2606
+ } else
2607
+ e.deltaMode === WheelEvent.DOM_DELTA_LINE ? d = e.deltaY : e.deltaMode === WheelEvent.DOM_DELTA_PAGE ? d = e.deltaY * this.rows : d = e.deltaY / 33;
2608
+ if (d !== 0) {
2609
+ const p = this.viewportY - d;
2610
+ this.smoothScrollTo(p);
2611
+ }
2612
+ }
2613
+ }, this.handleMouseDown = (e) => {
2614
+ if (!this.canvas || !this.renderer || !this.wasmTerm)
2615
+ return;
2616
+ const s = this.wasmTerm.getScrollbackLength();
2617
+ if (s === 0)
2618
+ return;
2619
+ const r = this.canvas.getBoundingClientRect(), n = e.clientX - r.left, o = e.clientY - r.top, a = r.width, l = r.height, h = 8, u = a - h - 4, d = 4;
2620
+ if (n >= u && n <= u + h) {
2621
+ e.preventDefault(), e.stopPropagation(), e.stopImmediatePropagation();
2622
+ const p = l - d * 2, m = this.rows, g = s + m, w = Math.max(20, m / g * p), S = this.viewportY / s, b = d + (p - w) * (1 - S);
2623
+ if (o >= b && o <= b + w)
2624
+ this.isDraggingScrollbar = !0, this.scrollbarDragStart = o, this.scrollbarDragStartViewportY = this.viewportY, this.canvas && (this.canvas.style.userSelect = "none", this.canvas.style.webkitUserSelect = "none");
2625
+ else {
2626
+ const v = 1 - (o - d) / p, C = Math.round(v * s);
2627
+ this.scrollToLine(Math.max(0, Math.min(s, C)));
2628
+ }
2629
+ }
2630
+ }, this.handleMouseUp = () => {
2631
+ this.isDraggingScrollbar && (this.isDraggingScrollbar = !1, this.scrollbarDragStart = null, this.canvas && (this.canvas.style.userSelect = "", this.canvas.style.webkitUserSelect = ""), this.scrollbarVisible && this.getScrollbackLength() > 0 && this.showScrollbar());
2632
+ }, this.currentTheme = { ...E, ...t.theme }, this.bootstrapBuffer = {
2633
+ getLine: (e) => {
2634
+ var s;
2635
+ return this.bootstrapCells && e >= 0 && e < this.bootstrapCells.length ? this.bootstrapCells[e] : ((s = this.wasmTerm) == null ? void 0 : s.getLine(e)) ?? null;
2636
+ },
2637
+ getCursor: () => {
2638
+ var e;
2639
+ return this.bootstrapCells ? { x: 0, y: 0, visible: !0 } : ((e = this.wasmTerm) == null ? void 0 : e.getCursor()) ?? { x: 0, y: 0, visible: !0 };
2640
+ },
2641
+ getDimensions: () => ({ cols: this.cols, rows: this.rows }),
2642
+ isRowDirty: (e) => {
2643
+ var s;
2644
+ return this.bootstrapDirty ? !0 : this.bootstrapCells ? !1 : ((s = this.wasmTerm) == null ? void 0 : s.isRowDirty(e)) ?? !1;
2645
+ },
2646
+ needsFullRedraw: () => {
2647
+ var s;
2648
+ if (this.bootstrapDirty)
2649
+ return !0;
2650
+ if (this.bootstrapCells)
2651
+ return !1;
2652
+ const e = this.wasmTerm;
2653
+ return ((s = e == null ? void 0 : e.needsFullRedraw) == null ? void 0 : s.call(e)) ?? !1;
2654
+ },
2655
+ clearDirty: () => {
2656
+ var e;
2657
+ this.bootstrapDirty = !1, (e = this.wasmTerm) == null || e.clearDirty();
2658
+ },
2659
+ getGraphemeString: (e, s) => {
2660
+ var n, o;
2661
+ if (this.bootstrapCells && e >= 0 && e < this.bootstrapCells.length) {
2662
+ const a = (n = this.bootstrapCells[e]) == null ? void 0 : n[s];
2663
+ return a ? String.fromCodePoint(a.codepoint || 32) : " ";
2664
+ }
2665
+ const r = this.wasmTerm;
2666
+ return ((o = r == null ? void 0 : r.getGraphemeString) == null ? void 0 : o.call(r, e, s)) ?? " ";
2667
+ }
2668
+ };
2669
+ }
2670
+ // Viewport and scrolling state (viewportY aliases TerminalCore._viewportY)
2671
+ get viewportY() {
2672
+ return this._viewportY;
2673
+ }
2674
+ set viewportY(t) {
2675
+ this._viewportY = t;
2676
+ }
2677
+ // ==========================================================================
2678
+ // Option Change Handling (browser-specific overrides)
2679
+ // ==========================================================================
2680
+ handleOptionChange(t, i, e) {
2681
+ if (i !== e)
2682
+ switch (t) {
2683
+ case "disableStdin":
2684
+ break;
2685
+ case "cursorBlink":
2686
+ case "cursorStyle":
2687
+ this.renderer && (this.renderer.setCursorStyle(this.options.cursorStyle), this.renderer.setCursorBlink(this.options.cursorBlink));
2688
+ break;
2689
+ case "theme":
2690
+ if (this.renderer && this.wasmTerm) {
2691
+ const s = i && typeof i == "object" ? i : {}, r = Object.keys(s).length > 0;
2692
+ this.currentTheme = r ? { ...this.currentTheme, ...s } : { ...E }, this.renderer.setTheme(this.currentTheme), this.wasmTerm.setColors(this.buildThemeColorsConfig(this.currentTheme));
2693
+ }
2694
+ break;
2695
+ case "fontSize":
2696
+ this.renderer && (this.renderer.setFontSize(this.options.fontSize), this.handleFontChange());
2697
+ break;
2698
+ case "fontFamily":
2699
+ this.renderer && (this.renderer.setFontFamily(this.options.fontFamily), this.handleFontChange());
2700
+ break;
2701
+ case "cols":
2702
+ case "rows":
2703
+ this.resize(this.options.cols, this.options.rows);
2704
+ break;
2705
+ }
2706
+ }
2707
+ handleFontChange() {
2708
+ if (!this.renderer || !this.wasmTerm || !this.canvas)
2709
+ return;
2710
+ this.selectionManager && this.selectionManager.clearSelection(), this.renderer.resize(this.cols, this.rows);
2711
+ const t = this.renderer.getMetrics();
2712
+ this.canvas.width = t.width * this.cols, this.canvas.height = t.height * this.rows, this.canvas.style.width = `${t.width * this.cols}px`, this.canvas.style.height = `${t.height * this.rows}px`, this.updateWasmPixelSize(), this.renderer.render(this.wasmTerm, !0, this.viewportY, this);
2713
+ }
2714
+ buildThemeColorsConfig(t) {
2715
+ return {
2716
+ fgColor: this.parseColorToHex(t.foreground),
2717
+ bgColor: this.parseColorToHex(t.background),
2718
+ cursorColor: this.parseColorToHex(t.cursor),
2719
+ palette: [
2720
+ this.parseColorToHex(t.black),
2721
+ this.parseColorToHex(t.red),
2722
+ this.parseColorToHex(t.green),
2723
+ this.parseColorToHex(t.yellow),
2724
+ this.parseColorToHex(t.blue),
2725
+ this.parseColorToHex(t.magenta),
2726
+ this.parseColorToHex(t.cyan),
2727
+ this.parseColorToHex(t.white),
2728
+ this.parseColorToHex(t.brightBlack),
2729
+ this.parseColorToHex(t.brightRed),
2730
+ this.parseColorToHex(t.brightGreen),
2731
+ this.parseColorToHex(t.brightYellow),
2732
+ this.parseColorToHex(t.brightBlue),
2733
+ this.parseColorToHex(t.brightMagenta),
2734
+ this.parseColorToHex(t.brightCyan),
2735
+ this.parseColorToHex(t.brightWhite)
2736
+ ]
2737
+ };
2738
+ }
2739
+ // ==========================================================================
2740
+ // Lifecycle Methods
2741
+ // ==========================================================================
2742
+ open(t) {
2743
+ if (this.isOpen)
2744
+ throw new Error("Terminal is already open");
2745
+ if (this.isDisposed)
2746
+ throw new Error("Terminal has been disposed");
2747
+ this.element = t, this.isOpen = !0;
2748
+ try {
2749
+ t.setAttribute("tabindex", "-1"), t.setAttribute("role", "textbox"), t.setAttribute("aria-label", "Terminal input"), t.setAttribute("aria-multiline", "true"), this.canvas = document.createElement("canvas"), this.canvas.style.display = "block", this.canvas.style.cursor = "text", t.appendChild(this.canvas), this.textarea = document.createElement("textarea"), this.textarea.setAttribute("autocorrect", "off"), this.textarea.setAttribute("autocapitalize", "off"), this.textarea.setAttribute("spellcheck", "false"), this.textarea.setAttribute("tabindex", "0"), this.textarea.setAttribute("aria-label", "Terminal input"), this.textarea.style.position = "absolute", this.textarea.style.left = "0", this.textarea.style.top = "0", this.textarea.style.width = "1px", this.textarea.style.height = "1px", this.textarea.style.padding = "0", this.textarea.style.border = "none", this.textarea.style.margin = "0", this.textarea.style.opacity = "0", this.textarea.style.clipPath = "inset(50%)", this.textarea.style.overflow = "hidden", this.textarea.style.whiteSpace = "nowrap", this.textarea.style.resize = "none", t.appendChild(this.textarea);
2750
+ const i = this.textarea;
2751
+ this.canvas.addEventListener("mousedown", (o) => {
2752
+ o.preventDefault(), i.focus();
2753
+ }), this.canvas.addEventListener("touchend", (o) => {
2754
+ o.preventDefault(), i.focus();
2755
+ }), t.addEventListener("mousedown", (o) => {
2756
+ o.target === t && (o.preventDefault(), i.focus());
2757
+ }), t.addEventListener("focus", () => {
2758
+ var o;
2759
+ i.focus(), (o = this.wasmTerm) != null && o.hasFocusEvents() && this.dataEmitter.fire("\x1B[I");
2760
+ }), t.addEventListener("blur", () => {
2761
+ var o;
2762
+ (o = this.wasmTerm) != null && o.hasFocusEvents() && this.dataEmitter.fire("\x1B[O");
2763
+ }), this.renderer = new it(this.canvas, {
2764
+ fontSize: this.options.fontSize,
2765
+ fontFamily: this.options.fontFamily,
2766
+ cursorStyle: this.options.cursorStyle,
2767
+ cursorBlink: this.options.cursorBlink,
2768
+ theme: this.options.theme
2769
+ }), this.renderer.resize(this.cols, this.rows), this.updateWasmPixelSize();
2770
+ const e = this.canvas, s = this.renderer, r = this.wasmTerm, n = {
2771
+ hasMouseTracking: () => (r == null ? void 0 : r.hasMouseTracking()) ?? !1,
2772
+ hasSgrMouseMode: () => (r == null ? void 0 : r.getMode(1006, !1)) ?? !0,
2773
+ getCellDimensions: () => ({
2774
+ width: s.charWidth,
2775
+ height: s.charHeight
2776
+ }),
2777
+ getCanvasOffset: () => {
2778
+ const o = e.getBoundingClientRect();
2779
+ return { left: o.left, top: o.top };
2780
+ }
2781
+ };
2782
+ this.inputHandler = new Q(
2783
+ this.ghostty,
2784
+ t,
2785
+ (o) => {
2786
+ var a;
2787
+ this.options.disableStdin || ((a = this.selectionManager) == null || a.clearSelection(), this.awaitingEcho = !0, this.dataEmitter.fire(o));
2788
+ },
2789
+ () => {
2790
+ this.bellEmitter.fire();
2791
+ },
2792
+ (o) => {
2793
+ this.keyEmitter.fire(o);
2794
+ },
2795
+ this.customKeyEventHandler,
2796
+ (o) => {
2797
+ var a;
2798
+ return ((a = this.wasmTerm) == null ? void 0 : a.getMode(o, !1)) ?? !1;
2799
+ },
2800
+ () => this.copySelection(),
2801
+ this.textarea,
2802
+ n
2803
+ ), this.selectionManager = new st(
2804
+ this,
2805
+ this.renderer,
2806
+ this.wasmTerm,
2807
+ this.textarea
2808
+ ), this.renderer.setSelectionManager(this.selectionManager), this.selectionManager.onSelectionChange(() => {
2809
+ this.selectionChangeEmitter.fire(), this.requestRender();
2810
+ }), this.linkDetector = new X(this), this.linkDetector.registerProvider(new J(this)), this.linkDetector.registerProvider(new j(this)), t.addEventListener("mousedown", this.handleMouseDown, { capture: !0 }), t.addEventListener("mousemove", this.handleMouseMove), t.addEventListener("mouseleave", this.handleMouseLeave), t.addEventListener("click", this.handleClick), document.addEventListener("mouseup", this.handleMouseUp), t.addEventListener("wheel", this.handleWheel, { passive: !1, capture: !0 }), this.armBootstrapBlank(), this.renderer.render(this.bootstrapBuffer, !0, this.viewportY, this, this.scrollbarOpacity), this.renderer.setOnRequestRender(() => this.requestRender()), this.renderTick(), this.options.focusOnOpen !== !1 && this.focus();
2811
+ } catch (i) {
2812
+ throw this.isOpen = !1, this.cleanupComponents(), new Error(`Failed to open terminal: ${i}`);
2813
+ }
2814
+ }
2815
+ // ==========================================================================
2816
+ // Write Methods (browser-specific override)
2817
+ // ==========================================================================
2818
+ write(t, i) {
2819
+ this.assertOpen(), this.options.convertEol && typeof t == "string" && (t = t.replace(/\n/g, `\r
2820
+ `)), typeof t == "string" && t.includes("\x1B]22;") && this.interceptOsc22(t), this.writeInternal(t, i);
2821
+ }
2822
+ stripUnimplementedTitleSequences(t) {
2823
+ if (typeof t == "string")
2824
+ return t.replace(/\x1bk[^\x1b\x07]*(?:\x1b\\|\x07)/g, "");
2825
+ let i = 0, e = -1, s = null;
2826
+ for (; i < t.length; ) {
2827
+ if (t[i] === 27 && i + 1 < t.length && t[i + 1] === 107) {
2828
+ let r = i + 2;
2829
+ for (; r < t.length; ) {
2830
+ if (t[r] === 7) {
2831
+ r++;
2832
+ break;
2833
+ }
2834
+ if (t[r] === 27 && r + 1 < t.length && t[r + 1] === 92) {
2835
+ r += 2;
2836
+ break;
2837
+ }
2838
+ r++;
2839
+ }
2840
+ s === null && (s = new Uint8Array(t.length), s.set(t.subarray(0, i)), e = i), i = r;
2841
+ continue;
2842
+ }
2843
+ s !== null && (s[e++] = t[i]), i++;
2844
+ }
2845
+ return s === null ? t : s.subarray(0, e);
2846
+ }
2847
+ writeInternal(t, i) {
2848
+ var o;
2849
+ this.disarmBootstrapBlank();
2850
+ const e = this.stripUnimplementedTitleSequences(t), s = this.options.preserveScrollOnWrite === !0, r = s ? this.viewportY : 0, n = s && r > 0 ? this.wasmTerm.getScrollbackLength() : 0;
2851
+ if (this.wasmTerm.write(e), this.options.emitTerminalResponses && this.processTerminalResponses(), typeof t == "string" && t.includes("\x07") ? this.bellEmitter.fire() : t instanceof Uint8Array && t.includes(7) && this.bellEmitter.fire(), (o = this.linkDetector) == null || o.invalidateCache(), s) {
2852
+ if (r > 0) {
2853
+ const a = this.wasmTerm.getScrollbackLength(), l = a - n, h = Math.max(0, Math.min(r + l, a));
2854
+ h !== r && (this.viewportY = h, this.scrollEmitter.fire(this.viewportY), a > 0 && this.showScrollbar());
2855
+ }
2856
+ } else
2857
+ this.viewportY !== 0 && this.scrollToBottom();
2858
+ typeof t == "string" && t.includes("\x1B]") && (this.checkForTitleChange(t), this.checkForShellIntegration(t)), typeof t == "string" && (t.includes(`
2859
+ `) || t.includes(`\r
2860
+ `)) ? this.lineFeedEmitter.fire() : t instanceof Uint8Array && t.includes(10) && this.lineFeedEmitter.fire(), this.checkCursorMove(), i ? requestAnimationFrame(() => {
2861
+ i(), this.writeParsedEmitter.fire();
2862
+ }) : this.writeParsedEmitter.fire(), this.awaitingEcho && this.renderer && this.wasmTerm && (this.awaitingEcho = !1, this.renderer.render(this.wasmTerm, !1, this.viewportY, this, this.scrollbarOpacity)), this.requestRender();
2863
+ }
2864
+ writeln(t, i) {
2865
+ if (typeof t == "string")
2866
+ this.write(t + `\r
2867
+ `, i);
2868
+ else {
2869
+ const e = new Uint8Array(t.length + 2);
2870
+ e.set(t), e[t.length] = 13, e[t.length + 1] = 10, this.write(e, i);
2871
+ }
2872
+ }
2873
+ paste(t) {
2874
+ this.assertOpen(), !this.options.disableStdin && (this.awaitingEcho = !0, this.wasmTerm.hasBracketedPaste() ? this.dataEmitter.fire("\x1B[200~" + t + "\x1B[201~") : this.dataEmitter.fire(t));
2875
+ }
2876
+ input(t, i = !1) {
2877
+ this.assertOpen(), !this.options.disableStdin && (i ? (this.awaitingEcho = !0, this.dataEmitter.fire(t)) : this.write(t));
2878
+ }
2879
+ // ==========================================================================
2880
+ // Resize (browser override with canvas/renderer resize)
2881
+ // ==========================================================================
2882
+ resize(t, i) {
2883
+ if (this.isDisposed)
2884
+ throw new Error("Terminal has been disposed");
2885
+ if (!this.wasmTerm)
2886
+ throw new Error("Terminal not initialized");
2887
+ if (!(t === this.cols && i === this.rows)) {
2888
+ this.isOpen && this.cancelRenderLoop();
2889
+ try {
2890
+ if (this.cols = t, this.rows = i, this.wasmTerm.resize(t, i), this.renderer && this.canvas) {
2891
+ this.renderer.resize(t, i);
2892
+ const e = this.renderer.getMetrics();
2893
+ this.canvas.width = e.width * t, this.canvas.height = e.height * i, this.canvas.style.width = `${e.width * t}px`, this.canvas.style.height = `${e.height * i}px`, this.updateWasmPixelSize(), this.renderer.render(this.wasmTerm, !0, this.viewportY, this);
2894
+ }
2895
+ this.resizeEmitter.fire({ cols: t, rows: i });
2896
+ } catch (e) {
2897
+ console.error("Terminal resize failed:", e);
2898
+ }
2899
+ this.isOpen && (this.flushWriteQueue(), this.requestRender());
2900
+ }
2901
+ }
2902
+ // ==========================================================================
2903
+ // Reset (browser override: recreates WASM terminal)
2904
+ // ==========================================================================
2905
+ reset() {
2906
+ this.assertOpen(), this.wasmTerm && this.wasmTerm.free();
2907
+ const t = this.buildWasmConfig();
2908
+ this.wasmTerm = this.ghostty.createTerminal(this.cols, this.rows, t), this.updateWasmPixelSize(), this.armBootstrapBlank(), this.renderer.clear(), this.renderer.render(this.bootstrapBuffer, !0, this.viewportY, this, this.scrollbarOpacity), this.currentTitle = "";
2909
+ }
2910
+ // ==========================================================================
2911
+ // Clear (browser override: same as core but needs assertOpen)
2912
+ // ==========================================================================
2913
+ clear() {
2914
+ this.assertOpen(), this.wasmTerm.write("\x1B[2J\x1B[H");
2915
+ }
2916
+ // ==========================================================================
2917
+ // Focus / Blur
2918
+ // ==========================================================================
2919
+ focus() {
2920
+ if (this.isOpen) {
2921
+ const t = this.textarea || this.element;
2922
+ t && (t.focus(), setTimeout(() => t == null ? void 0 : t.focus(), 0));
2923
+ }
2924
+ }
2925
+ blur() {
2926
+ this.isOpen && this.element && this.element.blur();
2927
+ }
2928
+ // ==========================================================================
2929
+ // Addon (browser override to use `this` as Terminal)
2930
+ // ==========================================================================
2931
+ loadAddon(t) {
2932
+ t.activate(this), this.addons.push(t);
2933
+ }
2934
+ // ==========================================================================
2935
+ // Terminal Modes (browser override: no assertOpen needed after headless port)
2936
+ // ==========================================================================
2937
+ getMode(t, i = !1) {
2938
+ return this.wasmTerm ? this.wasmTerm.getMode(t, i) : !1;
2939
+ }
2940
+ hasBracketedPaste() {
2941
+ return this.wasmTerm ? this.wasmTerm.hasBracketedPaste() : !1;
2942
+ }
2943
+ hasFocusEvents() {
2944
+ return this.wasmTerm ? this.wasmTerm.hasFocusEvents() : !1;
2945
+ }
2946
+ hasMouseTracking() {
2947
+ return this.wasmTerm ? this.wasmTerm.hasMouseTracking() : !1;
2948
+ }
2949
+ // ==========================================================================
2950
+ // Selection API
2951
+ // ==========================================================================
2952
+ getSelection() {
2953
+ var t;
2954
+ return ((t = this.selectionManager) == null ? void 0 : t.getSelection()) || "";
2955
+ }
2956
+ hasSelection() {
2957
+ var t;
2958
+ return ((t = this.selectionManager) == null ? void 0 : t.hasSelection()) || !1;
2959
+ }
2960
+ clearSelection() {
2961
+ var t;
2962
+ (t = this.selectionManager) == null || t.clearSelection();
2963
+ }
2964
+ copySelection() {
2965
+ var t;
2966
+ return ((t = this.selectionManager) == null ? void 0 : t.copySelection()) || !1;
2967
+ }
2968
+ selectAll() {
2969
+ var t;
2970
+ (t = this.selectionManager) == null || t.selectAll();
2971
+ }
2972
+ select(t, i, e) {
2973
+ var s;
2974
+ (s = this.selectionManager) == null || s.select(t, i, e);
2975
+ }
2976
+ selectLines(t, i) {
2977
+ var e;
2978
+ (e = this.selectionManager) == null || e.selectLines(t, i);
2979
+ }
2980
+ getSelectionPosition() {
2981
+ var t;
2982
+ return (t = this.selectionManager) == null ? void 0 : t.getSelectionPosition();
2983
+ }
2984
+ // ==========================================================================
2985
+ // Custom Event Handlers
2986
+ // ==========================================================================
2987
+ attachCustomKeyEventHandler(t) {
2988
+ this.customKeyEventHandler = t, this.inputHandler && this.inputHandler.setCustomKeyEventHandler(t);
2989
+ }
2990
+ attachCustomWheelEventHandler(t) {
2991
+ this.customWheelEventHandler = t;
2992
+ }
2993
+ // ==========================================================================
2994
+ // Link Detection
2995
+ // ==========================================================================
2996
+ registerLinkProvider(t) {
2997
+ if (!this.linkDetector)
2998
+ throw new Error("Terminal must be opened before registering link providers");
2999
+ this.linkDetector.registerProvider(t);
3000
+ }
3001
+ // ==========================================================================
3002
+ // Scrolling (browser override: adds showScrollbar + requestRender)
3003
+ // ==========================================================================
3004
+ scrollLines(t) {
3005
+ if (!this.wasmTerm)
3006
+ throw new Error("Terminal not open");
3007
+ const i = this.getScrollbackLength(), e = Math.max(0, Math.min(i, this.viewportY - t));
3008
+ e !== this.viewportY && (this.viewportY = e, this.scrollEmitter.fire(this.viewportY), i > 0 && this.showScrollbar(), this.requestRender());
3009
+ }
3010
+ scrollPages(t) {
3011
+ this.scrollLines(t * this.rows);
3012
+ }
3013
+ scrollToTop() {
3014
+ const t = this.getScrollbackLength();
3015
+ t > 0 && this.viewportY !== t && (this.viewportY = t, this.scrollEmitter.fire(this.viewportY), this.showScrollbar(), this.requestRender());
3016
+ }
3017
+ scrollToBottom() {
3018
+ this.viewportY !== 0 && (this.viewportY = 0, this.scrollEmitter.fire(this.viewportY), this.getScrollbackLength() > 0 && this.showScrollbar(), this.requestRender());
3019
+ }
3020
+ scrollToLine(t) {
3021
+ const i = this.getScrollbackLength(), e = Math.max(0, Math.min(i, t));
3022
+ e !== this.viewportY && (this.viewportY = e, this.scrollEmitter.fire(this.viewportY), i > 0 && this.showScrollbar(), this.requestRender());
3023
+ }
3024
+ // ==========================================================================
3025
+ // Dispose (browser override: cleans up DOM)
3026
+ // ==========================================================================
3027
+ dispose() {
3028
+ this.isDisposed || (this.isOpen = !1, this.cancelRenderLoop(), this.writeQueue.length = 0, this.scrollAnimationFrame && (cancelAnimationFrame(this.scrollAnimationFrame), this.scrollAnimationFrame = void 0), this.mouseMoveThrottleTimeout && (clearTimeout(this.mouseMoveThrottleTimeout), this.mouseMoveThrottleTimeout = void 0), this.pendingMouseMove = void 0, this.cleanupComponents(), this.selectionChangeEmitter.dispose(), this.keyEmitter.dispose(), this.renderEmitter.dispose(), this.mouseCursorChangeEmitter.dispose(), super.dispose());
3029
+ }
3030
+ // ==========================================================================
3031
+ // processTerminalResponses (browser override: drain all pending responses)
3032
+ // ==========================================================================
3033
+ processTerminalResponses() {
3034
+ if (this.wasmTerm)
3035
+ for (; ; ) {
3036
+ const t = this.wasmTerm.readResponse();
3037
+ if (t === null)
3038
+ break;
3039
+ this.dataEmitter.fire(t);
3040
+ }
3041
+ }
3042
+ // ==========================================================================
3043
+ // Private Browser Methods
3044
+ // ==========================================================================
3045
+ updateWasmPixelSize() {
3046
+ if (!this.renderer || !this.wasmTerm)
3047
+ return;
3048
+ const t = this.renderer.getMetrics();
3049
+ this.wasmTerm.setCellPixelSize(t.width, t.height);
3050
+ }
3051
+ cancelRenderLoop() {
3052
+ this.animationFrameId && (cancelAnimationFrame(this.animationFrameId), this.animationFrameId = void 0);
3053
+ }
3054
+ flushWriteQueue() {
3055
+ for (; this.writeQueue.length > 0; ) {
3056
+ const t = this.writeQueue.shift();
3057
+ this.wasmTerm.write(t);
3058
+ }
3059
+ }
3060
+ requestRender() {
3061
+ this.animationFrameId === void 0 && (this.isDisposed || !this.isOpen || (this.animationFrameId = requestAnimationFrame(this.renderTick)));
3062
+ }
3063
+ syncTextareaToCursor(t, i) {
3064
+ if (!this.textarea || !this.renderer)
3065
+ return;
3066
+ const e = this.renderer.charWidth, s = this.renderer.charHeight;
3067
+ !e || !s || (this.textarea.style.left = `${t * e}px`, this.textarea.style.top = `${i * s}px`);
3068
+ }
3069
+ /**
3070
+ * Intercept OSC 22 mouse-cursor-shape sequences emitted by the PTY.
3071
+ * Updates the canvas CSS cursor and fires onMouseCursorChange.
3072
+ *
3073
+ * Format: ESC ] 22 ; <w3c-cursor-name> BEL|ST
3074
+ *
3075
+ * Ghostty's MouseShape names map 1-to-1 to W3C CSS cursor values after
3076
+ * replacing underscores with hyphens (e.g. "context_menu" → "context-menu").
3077
+ */
3078
+ interceptOsc22(t) {
3079
+ const i = /\x1b\]22;([^\x07\x1b]*)(?:\x07|\x1b\\)/g;
3080
+ let e;
3081
+ for (; (e = i.exec(t)) !== null; ) {
3082
+ const s = e[1].replace(/_/g, "-") || "default";
3083
+ s !== this.lastOsc22Cursor && (this.lastOsc22Cursor = s, this.canvas && (this.canvas.style.cursor = s), this.element && (this.element.style.cursor = s), this.mouseCursorChangeEmitter.fire(s));
3084
+ }
3085
+ }
3086
+ armBootstrapBlank() {
3087
+ const t = { ...E, ...this.options.theme };
3088
+ this.bootstrapCells = rt(this.cols, this.rows, {
3089
+ foreground: t.foreground,
3090
+ background: t.background
3091
+ }), this.bootstrapDirty = !0;
3092
+ }
3093
+ disarmBootstrapBlank() {
3094
+ this.bootstrapCells && (this.bootstrapCells = null, this.bootstrapDirty = !0);
3095
+ }
3096
+ cleanupComponents() {
3097
+ this.selectionManager && (this.selectionManager.dispose(), this.selectionManager = void 0), this.inputHandler && (this.inputHandler.dispose(), this.inputHandler = void 0), this.renderer && (this.renderer.dispose(), this.renderer = void 0), this.canvas && this.canvas.parentNode && (this.canvas.parentNode.removeChild(this.canvas), this.canvas = void 0), this.textarea && this.textarea.parentNode && (this.textarea.parentNode.removeChild(this.textarea), this.textarea = void 0), this.element && (this.element.removeEventListener("wheel", this.handleWheel), this.element.removeEventListener("mousedown", this.handleMouseDown, { capture: !0 }), this.element.removeEventListener("mousemove", this.handleMouseMove), this.element.removeEventListener("mouseleave", this.handleMouseLeave), this.element.removeEventListener("click", this.handleClick), this.element.removeAttribute("role"), this.element.removeAttribute("aria-label"), this.element.removeAttribute("aria-multiline")), this.isOpen && typeof document < "u" && document.removeEventListener("mouseup", this.handleMouseUp), this.scrollbarHideTimeout && (window.clearTimeout(this.scrollbarHideTimeout), this.scrollbarHideTimeout = void 0), this.linkDetector && (this.linkDetector.dispose(), this.linkDetector = void 0), this.element = void 0, this.textarea = void 0;
3098
+ }
3099
+ assertOpen() {
3100
+ if (this.isDisposed)
3101
+ throw new Error("Terminal has been disposed");
3102
+ if (!this.isOpen)
3103
+ throw new Error("Terminal must be opened before use. Call terminal.open(parent) first.");
3104
+ }
3105
+ // ==========================================================================
3106
+ // Smooth Scrolling
3107
+ // ==========================================================================
3108
+ smoothScrollTo(t) {
3109
+ if (!this.wasmTerm)
3110
+ return;
3111
+ const i = this.getScrollbackLength(), e = Math.max(0, Math.min(i, t));
3112
+ if ((this.options.smoothScrollDuration ?? 100) === 0) {
3113
+ this.viewportY = e, this.targetViewportY = e, this.scrollEmitter.fire(Math.floor(this.viewportY)), i > 0 && this.showScrollbar(), this.requestRender();
3114
+ return;
3115
+ }
3116
+ this.targetViewportY = e, !this.scrollAnimationFrame && (this.scrollAnimationStartTime = Date.now(), this.scrollAnimationStartY = this.viewportY, this.animateScroll());
3117
+ }
3118
+ // ==========================================================================
3119
+ // Scrollbar Visibility
3120
+ // ==========================================================================
3121
+ showScrollbar() {
3122
+ this.scrollbarHideTimeout && (window.clearTimeout(this.scrollbarHideTimeout), this.scrollbarHideTimeout = void 0), this.scrollbarVisible ? this.scrollbarOpacity = 1 : (this.scrollbarVisible = !0, this.scrollbarOpacity = 0, this.fadeInScrollbar()), this.isDraggingScrollbar || (this.scrollbarHideTimeout = window.setTimeout(() => {
3123
+ this.hideScrollbar();
3124
+ }, this.SCROLLBAR_HIDE_DELAY_MS));
3125
+ }
3126
+ hideScrollbar() {
3127
+ this.scrollbarHideTimeout && (window.clearTimeout(this.scrollbarHideTimeout), this.scrollbarHideTimeout = void 0), this.scrollbarVisible && this.fadeOutScrollbar();
3128
+ }
3129
+ fadeInScrollbar() {
3130
+ const t = Date.now(), i = () => {
3131
+ const e = Date.now() - t, s = Math.min(e / this.SCROLLBAR_FADE_DURATION_MS, 1);
3132
+ this.scrollbarOpacity = s, this.renderer && this.wasmTerm && this.renderer.render(this.wasmTerm, !1, this.viewportY, this, this.scrollbarOpacity), s < 1 && requestAnimationFrame(i);
3133
+ };
3134
+ i();
3135
+ }
3136
+ fadeOutScrollbar() {
3137
+ const t = Date.now(), i = this.scrollbarOpacity, e = () => {
3138
+ const s = Date.now() - t, r = Math.min(s / this.SCROLLBAR_FADE_DURATION_MS, 1);
3139
+ this.scrollbarOpacity = i * (1 - r), this.renderer && this.wasmTerm && this.renderer.render(this.wasmTerm, !1, this.viewportY, this, this.scrollbarOpacity), r < 1 ? requestAnimationFrame(e) : (this.scrollbarVisible = !1, this.scrollbarOpacity = 0, this.renderer && this.wasmTerm && this.renderer.render(this.wasmTerm, !1, this.viewportY, this, 0));
3140
+ };
3141
+ e();
3142
+ }
3143
+ processMouseMove(t) {
3144
+ if (!this.canvas || !this.renderer || !this.linkDetector || !this.wasmTerm)
3145
+ return;
3146
+ const i = this.canvas.getBoundingClientRect(), e = Math.floor((t.clientX - i.left) / this.renderer.charWidth), r = Math.floor((t.clientY - i.top) / this.renderer.charHeight);
3147
+ let n = 0, o = null;
3148
+ const a = this.getViewportY(), l = Math.max(0, Math.floor(a));
3149
+ if (l > 0) {
3150
+ const g = this.wasmTerm.getScrollbackLength();
3151
+ if (r < l) {
3152
+ const w = g - l + r;
3153
+ o = this.wasmTerm.getScrollbackLine(w);
3154
+ } else {
3155
+ const w = r - l;
3156
+ o = this.wasmTerm.getLine(w);
3157
+ }
3158
+ } else
3159
+ o = this.wasmTerm.getLine(r);
3160
+ o && e >= 0 && e < o.length && (n = o[e].hyperlink_id);
3161
+ const h = this.renderer.hoveredHyperlinkId || 0;
3162
+ n !== h && this.renderer.setHoveredHyperlinkId(n);
3163
+ const u = this.wasmTerm.getScrollbackLength();
3164
+ let d;
3165
+ const p = this.getViewportY(), m = Math.max(0, Math.floor(p));
3166
+ if (m > 0)
3167
+ if (r < m)
3168
+ d = u - m + r;
3169
+ else {
3170
+ const g = r - m;
3171
+ d = u + g;
3172
+ }
3173
+ else
3174
+ d = u + r;
3175
+ this.linkDetector.getLinkAt(e, d).then((g) => {
3176
+ var w, S, b, f;
3177
+ if (g !== this.currentHoveredLink) {
3178
+ (S = (w = this.currentHoveredLink) == null ? void 0 : w.hover) == null || S.call(w, !1), this.currentHoveredLink = g, (b = g == null ? void 0 : g.hover) == null || b.call(g, !0);
3179
+ const v = g ? "pointer" : "text";
3180
+ if (this.element && (this.element.style.cursor = v), this.canvas && (this.canvas.style.cursor = v), this.renderer)
3181
+ if (g) {
3182
+ const C = ((f = this.wasmTerm) == null ? void 0 : f.getScrollbackLength()) || 0, M = this.getViewportY(), F = Math.max(0, Math.floor(M)), O = g.range.start.y - C + F, K = g.range.end.y - C + F;
3183
+ O < this.rows && K >= 0 ? this.renderer.setHoveredLinkRange({
3184
+ startX: g.range.start.x,
3185
+ startY: Math.max(0, O),
3186
+ endX: g.range.end.x,
3187
+ endY: Math.min(this.rows - 1, K)
3188
+ }) : this.renderer.setHoveredLinkRange(null);
3189
+ } else
3190
+ this.renderer.setHoveredLinkRange(null);
3191
+ }
3192
+ }).catch((g) => {
3193
+ console.warn("Link detection error:", g);
3194
+ });
3195
+ }
3196
+ processScrollbarDrag(t) {
3197
+ if (!this.canvas || !this.renderer || !this.wasmTerm || this.scrollbarDragStart === null)
3198
+ return;
3199
+ const i = this.wasmTerm.getScrollbackLength();
3200
+ if (i === 0)
3201
+ return;
3202
+ const e = this.canvas.getBoundingClientRect(), r = t.clientY - e.top - this.scrollbarDragStart, a = e.height - 4 * 2, l = this.rows, h = i + l, u = Math.max(20, l / h * a), d = -r / (a - u), p = Math.round(d * i), m = this.scrollbarDragStartViewportY + p;
3203
+ this.scrollToLine(Math.max(0, Math.min(i, m)));
3204
+ }
3205
+ };
3206
+ W.SYNC_OUTPUT_TIMEOUT_MS = 500;
3207
+ let ut = W;
3208
+ const nt = 2, ot = 1, at = 15, ht = 100;
3209
+ class dt {
3210
+ constructor() {
3211
+ this._isResizing = !1;
3212
+ }
3213
+ /**
3214
+ * Activate the addon (called by Terminal.loadAddon)
3215
+ */
3216
+ activate(t) {
3217
+ this._terminal = t;
3218
+ }
3219
+ /**
3220
+ * Dispose the addon and clean up resources
3221
+ */
3222
+ dispose() {
3223
+ this._resizeObserver && (this._resizeObserver.disconnect(), this._resizeObserver = void 0), this._resizeDebounceTimer && (clearTimeout(this._resizeDebounceTimer), this._resizeDebounceTimer = void 0), this._lastCols = void 0, this._lastRows = void 0, this._terminal = void 0;
3224
+ }
3225
+ /**
3226
+ * Fit the terminal to its container
3227
+ *
3228
+ * Calculates optimal dimensions and resizes the terminal.
3229
+ * Does nothing if dimensions cannot be calculated or haven't changed.
3230
+ */
3231
+ fit() {
3232
+ if (this._isResizing)
3233
+ return;
3234
+ const t = this.proposeDimensions();
3235
+ if (!t || !this._terminal)
3236
+ return;
3237
+ const i = this._terminal, e = i.cols, s = i.rows;
3238
+ if (!(t.cols === this._lastCols && t.rows === this._lastRows || t.cols === e && t.rows === s)) {
3239
+ this._lastCols = t.cols, this._lastRows = t.rows, this._isResizing = !0;
3240
+ try {
3241
+ i.resize && typeof i.resize == "function" && i.resize(t.cols, t.rows);
3242
+ } finally {
3243
+ setTimeout(() => {
3244
+ this._isResizing = !1;
3245
+ }, 50);
3246
+ }
3247
+ }
3248
+ }
3249
+ /**
3250
+ * Propose dimensions to fit the terminal to its container
3251
+ *
3252
+ * Calculates cols and rows based on:
3253
+ * - Terminal container element dimensions (clientWidth/Height)
3254
+ * - Terminal element padding
3255
+ * - Font metrics (character cell size)
3256
+ * - Scrollbar width reservation
3257
+ *
3258
+ * @returns Proposed dimensions or undefined if cannot calculate
3259
+ */
3260
+ proposeDimensions() {
3261
+ var w;
3262
+ if (!((w = this._terminal) != null && w.element))
3263
+ return;
3264
+ const i = this._terminal.renderer;
3265
+ if (!i || typeof i.getMetrics != "function")
3266
+ return;
3267
+ const e = i.getMetrics();
3268
+ if (!e || e.width === 0 || e.height === 0)
3269
+ return;
3270
+ const s = this._terminal.element;
3271
+ if (typeof s.clientWidth > "u")
3272
+ return;
3273
+ const r = window.getComputedStyle(s), n = Number.parseInt(r.getPropertyValue("padding-top")) || 0, o = Number.parseInt(r.getPropertyValue("padding-bottom")) || 0, a = Number.parseInt(r.getPropertyValue("padding-left")) || 0, l = Number.parseInt(r.getPropertyValue("padding-right")) || 0, h = s.clientWidth, u = s.clientHeight;
3274
+ if (h === 0 || u === 0)
3275
+ return;
3276
+ const d = h - a - l - at, p = u - n - o, m = Math.max(nt, Math.floor(d / e.width)), g = Math.max(ot, Math.floor(p / e.height));
3277
+ return { cols: m, rows: g };
3278
+ }
3279
+ /**
3280
+ * Observe the terminal's container for resize events
3281
+ *
3282
+ * Sets up a ResizeObserver to automatically call fit() when the
3283
+ * container size changes. Resize events are debounced to avoid
3284
+ * excessive calls during window drag operations.
3285
+ *
3286
+ * Call dispose() to stop observing.
3287
+ */
3288
+ observeResize() {
3289
+ var t;
3290
+ (t = this._terminal) != null && t.element && (this._resizeObserver || (this._resizeObserver = new ResizeObserver((i) => {
3291
+ this._isResizing || !i[0] || (this._resizeDebounceTimer && clearTimeout(this._resizeDebounceTimer), this._resizeDebounceTimer = setTimeout(() => {
3292
+ this.fit();
3293
+ }, ht));
3294
+ }), this._resizeObserver.observe(this._terminal.element)));
3295
+ }
3296
+ }
3297
+ class mt {
3298
+ constructor() {
3299
+ this._pasteListener = null, this._emitter = new k(), this.onImagePaste = this._emitter.event;
3300
+ }
3301
+ /**
3302
+ * Activate the addon (called by Terminal.loadAddon)
3303
+ */
3304
+ activate(t) {
3305
+ this._terminal = t;
3306
+ const i = t.element;
3307
+ i && this._attachListener(i);
3308
+ }
3309
+ /**
3310
+ * Dispose the addon and clean up resources
3311
+ */
3312
+ dispose() {
3313
+ this._detachListener(), this._emitter.dispose(), this._terminal = void 0;
3314
+ }
3315
+ _attachListener(t) {
3316
+ this._pasteListener = (i) => {
3317
+ const e = i.clipboardData;
3318
+ if (e != null && e.items) {
3319
+ for (const s of Array.from(e.items))
3320
+ if (s.kind === "file" && s.type.startsWith("image/")) {
3321
+ const r = s.getAsFile();
3322
+ if (r) {
3323
+ i.preventDefault(), i.stopPropagation();
3324
+ const n = new FileReader();
3325
+ n.onload = () => {
3326
+ const a = n.result.split(",")[1];
3327
+ if (a) {
3328
+ const l = r.type.split("/")[1] || "png";
3329
+ this._emitter.fire({
3330
+ name: `clipboard_${Date.now()}.${l}`,
3331
+ dataBase64: a
3332
+ });
3333
+ }
3334
+ }, n.readAsDataURL(r);
3335
+ return;
3336
+ }
3337
+ }
3338
+ }
3339
+ }, t.addEventListener("paste", this._pasteListener);
3340
+ }
3341
+ _detachListener() {
3342
+ var t;
3343
+ this._pasteListener && ((t = this._terminal) != null && t.element) && this._terminal.element.removeEventListener("paste", this._pasteListener), this._pasteListener = null;
3344
+ }
3345
+ }
3346
+ let P = null;
3347
+ async function ft() {
3348
+ P || (P = await q.load());
3349
+ }
3350
+ function lt() {
3351
+ if (!P)
3352
+ throw new Error(
3353
+ `ghostty-web not initialized. Call init() before creating Terminal instances.
3354
+ Example:
3355
+ import { init, Terminal } from "ghostty-web";
3356
+ await init();
3357
+ const term = new Terminal();
3358
+
3359
+ For tests, pass a Ghostty instance directly:
3360
+ import { Ghostty, Terminal } from "ghostty-web";
3361
+ const ghostty = await Ghostty.load();
3362
+ const term = new Terminal({ ghostty });`
3363
+ );
3364
+ return P;
3365
+ }
3366
+ export {
3367
+ it as CanvasRenderer,
3368
+ x as CellFlags,
3369
+ wt as DirtyState,
3370
+ k as EventEmitter,
3371
+ dt as FitAddon,
3372
+ q as Ghostty,
3373
+ bt as GhosttyTerminal,
3374
+ mt as ImagePasteAddon,
3375
+ Q as InputHandler,
3376
+ c as Key,
3377
+ Y as KeyAction,
3378
+ vt as KeyEncoder,
3379
+ A as KeyEncoderOption,
3380
+ X as LinkDetector,
3381
+ T as Mods,
3382
+ J as OSC8LinkProvider,
3383
+ st as SelectionManager,
3384
+ ut as Terminal,
3385
+ G as TerminalCore,
3386
+ j as UrlRegexProvider,
3387
+ lt as getGhostty,
3388
+ ft as init
3389
+ };