@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.
- package/LICENSE +21 -0
- package/README.md +314 -0
- package/dist/__vite-browser-external-2447137e.js +4 -0
- package/dist/__vite-browser-external-b3701507.cjs +1 -0
- package/dist/ghostty-vt.wasm +0 -0
- package/dist/ghostty-web.cjs.js +15 -0
- package/dist/ghostty-web.d.ts +2445 -0
- package/dist/ghostty-web.es.js +3389 -0
- package/dist/headless.cjs.js +5 -0
- package/dist/headless.d.ts +1271 -0
- package/dist/headless.es.js +34 -0
- package/dist/terminal-core-0895062a.cjs +5 -0
- package/dist/terminal-core-b557858d.js +4708 -0
- package/ghostty-vt.wasm +0 -0
- package/package.json +97 -0
|
@@ -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
|
+
};
|