var m = /* @__PURE__ */ ((E) => (E[E.CURSOR_KEY_APPLICATION = 0] = "CURSOR_KEY_APPLICATION", E[E.KEYPAD_KEY_APPLICATION = 1] = "KEYPAD_KEY_APPLICATION", E[E.IGNORE_KEYPAD_WITH_NUMLOCK = 2] = "IGNORE_KEYPAD_WITH_NUMLOCK", E[E.ALT_ESC_PREFIX = 3] = "ALT_ESC_PREFIX", E[E.MODIFY_OTHER_KEYS_STATE_2 = 4] = "MODIFY_OTHER_KEYS_STATE_2", E[E.KITTY_KEYBOARD_FLAGS = 5] = "KITTY_KEYBOARD_FLAGS", E))(m || {}), b = /* @__PURE__ */ ((E) => (E[E.RELEASE = 0] = "RELEASE", E[E.PRESS = 1] = "PRESS", E[E.REPEAT = 2] = "REPEAT", E))(b || {}), D = /* @__PURE__ */ ((E) => (E[E.A = 4] = "A", E[E.B = 5] = "B", E[E.C = 6] = "C", E[E.D = 7] = "D", E[E.E = 8] = "E", E[E.F = 9] = "F", E[E.G = 10] = "G", E[E.H = 11] = "H", E[E.I = 12] = "I", E[E.J = 13] = "J", E[E.K = 14] = "K", E[E.L = 15] = "L", E[E.M = 16] = "M", E[E.N = 17] = "N", E[E.O = 18] = "O", E[E.P = 19] = "P", E[E.Q = 20] = "Q", E[E.R = 21] = "R", E[E.S = 22] = "S", E[E.T = 23] = "T", E[E.U = 24] = "U", E[E.V = 25] = "V", E[E.W = 26] = "W", E[E.X = 27] = "X", E[E.Y = 28] = "Y", E[E.Z = 29] = "Z", E[E.ONE = 30] = "ONE", E[E.TWO = 31] = "TWO", E[E.THREE = 32] = "THREE", E[E.FOUR = 33] = "FOUR", E[E.FIVE = 34] = "FIVE", E[E.SIX = 35] = "SIX", E[E.SEVEN = 36] = "SEVEN", E[E.EIGHT = 37] = "EIGHT", E[E.NINE = 38] = "NINE", E[E.ZERO = 39] = "ZERO", E[E.ENTER = 40] = "ENTER", E[E.ESCAPE = 41] = "ESCAPE", E[E.BACKSPACE = 42] = "BACKSPACE", E[E.TAB = 43] = "TAB", E[E.SPACE = 44] = "SPACE", E[E.MINUS = 45] = "MINUS", E[E.EQUAL = 46] = "EQUAL", E[E.BRACKET_LEFT = 47] = "BRACKET_LEFT", E[E.BRACKET_RIGHT = 48] = "BRACKET_RIGHT", E[E.BACKSLASH = 49] = "BACKSLASH", E[E.SEMICOLON = 51] = "SEMICOLON", E[E.QUOTE = 52] = "QUOTE", E[E.GRAVE = 53] = "GRAVE", E[E.COMMA = 54] = "COMMA", E[E.PERIOD = 55] = "PERIOD", E[E.SLASH = 56] = "SLASH", E[E.CAPS_LOCK = 57] = "CAPS_LOCK", E[E.F1 = 58] = "F1", E[E.F2 = 59] = "F2", E[E.F3 = 60] = "F3", E[E.F4 = 61] = "F4", E[E.F5 = 62] = "F5", E[E.F6 = 63] = "F6", E[E.F7 = 64] = "F7", E[E.F8 = 65] = "F8", E[E.F9 = 66] = "F9", E[E.F10 = 67] = "F10", E[E.F11 = 68] = "F11", E[E.F12 = 69] = "F12", E[E.PRINT_SCREEN = 70] = "PRINT_SCREEN", E[E.SCROLL_LOCK = 71] = "SCROLL_LOCK", E[E.PAUSE = 72] = "PAUSE", E[E.INSERT = 73] = "INSERT", E[E.HOME = 74] = "HOME", E[E.PAGE_UP = 75] = "PAGE_UP", E[E.DELETE = 76] = "DELETE", E[E.END = 77] = "END", E[E.PAGE_DOWN = 78] = "PAGE_DOWN", E[E.RIGHT = 79] = "RIGHT", E[E.LEFT = 80] = "LEFT", E[E.DOWN = 81] = "DOWN", E[E.UP = 82] = "UP", E[E.NUM_LOCK = 83] = "NUM_LOCK", E[E.KP_DIVIDE = 84] = "KP_DIVIDE", E[E.KP_MULTIPLY = 85] = "KP_MULTIPLY", E[E.KP_MINUS = 86] = "KP_MINUS", E[E.KP_PLUS = 87] = "KP_PLUS", E[E.KP_ENTER = 88] = "KP_ENTER", E[E.KP_1 = 89] = "KP_1", E[E.KP_2 = 90] = "KP_2", E[E.KP_3 = 91] = "KP_3", E[E.KP_4 = 92] = "KP_4", E[E.KP_5 = 93] = "KP_5", E[E.KP_6 = 94] = "KP_6", E[E.KP_7 = 95] = "KP_7", E[E.KP_8 = 96] = "KP_8", E[E.KP_9 = 97] = "KP_9", E[E.KP_0 = 98] = "KP_0", E[E.KP_PERIOD = 99] = "KP_PERIOD", E[E.NON_US_BACKSLASH = 100] = "NON_US_BACKSLASH", E[E.APPLICATION = 101] = "APPLICATION", E[E.F13 = 104] = "F13", E[E.F14 = 105] = "F14", E[E.F15 = 106] = "F15", E[E.F16 = 107] = "F16", E[E.F17 = 108] = "F17", E[E.F18 = 109] = "F18", E[E.F19 = 110] = "F19", E[E.F20 = 111] = "F20", E[E.F21 = 112] = "F21", E[E.F22 = 113] = "F22", E[E.F23 = 114] = "F23", E[E.F24 = 115] = "F24", E))(D || {}), q = /* @__PURE__ */ ((E) => (E[E.NONE = 0] = "NONE", E[E.SHIFT = 1] = "SHIFT", E[E.CTRL = 2] = "CTRL", E[E.ALT = 4] = "ALT", E[E.SUPER = 8] = "SUPER", E[E.CAPSLOCK = 16] = "CAPSLOCK", E[E.NUMLOCK = 32] = "NUMLOCK", E))(q || {});
const d = 80;
var U = /* @__PURE__ */ ((E) => (E[E.BOLD = 1] = "BOLD", E[E.ITALIC = 2] = "ITALIC", E[E.UNDERLINE = 4] = "UNDERLINE", E[E.STRIKETHROUGH = 8] = "STRIKETHROUGH", E[E.INVERSE = 16] = "INVERSE", E[E.INVISIBLE = 32] = "INVISIBLE", E[E.BLINK = 64] = "BLINK", E[E.FAINT = 128] = "FAINT", E))(U || {});
class x {
constructor(A) {
this.exports = A.exports, this.memory = this.exports.memory;
}
/**
* Get current memory buffer (may change when memory grows)
*/
getBuffer() {
return this.memory.buffer;
}
/**
* Create a key encoder instance
*/
createKeyEncoder() {
return new z(this.exports);
}
/**
* Create a terminal emulator instance
*/
createTerminal(A = 80, B = 24, Q) {
return new j(this.exports, this.memory, A, B, Q);
}
/**
* Load Ghostty WASM from URL or file path
* If no path is provided, attempts to load from common default locations
*/
static async load(A) {
const B = [
// Inline base64 wasm defaults removed (tcell de-inline): the wasm ships as the
// separate ghostty-vt.wasm and is loaded by URL. Callers should pass an explicit
// URL to Ghostty.load(); the relative paths below remain as no-argument fallbacks.
// When used from CDN or local dev
"./ghostty-vt.wasm",
"/ghostty-vt.wasm"
], Q = A ? [A] : B;
let g = null;
for (const C of Q)
try {
let I;
const i = await fetch(C);
if (!i.ok)
throw new Error(`Failed to fetch WASM: ${i.status} ${i.statusText}`);
if (I = await i.arrayBuffer(), I.byteLength === 0)
throw new Error(`WASM file is empty (0 bytes). Check path: ${C}`);
const w = await WebAssembly.instantiate(I, {
env: {
log: (o, i) => {
const M = w.instance, k = new Uint8Array(M.exports.memory.buffer, o, i), G = new TextDecoder().decode(k);
console.log("[ghostty-wasm]", G);
}
}
});
return new x(w.instance);
} catch (I) {
g = I instanceof Error ? I : new Error(String(I));
}
throw new Error(
`Failed to load ghostty-vt.wasm. Tried paths: ${Q.join(", ")}. Last error: ${g == null ? void 0 : g.message}. You can specify a custom path with: new Terminal({ wasmPath: './path/to/ghostty-vt.wasm' })`
);
}
}
class z {
constructor(A) {
this.encoder = 0, this.exports = A;
const B = this.exports.ghostty_wasm_alloc_opaque(), Q = this.exports.ghostty_key_encoder_new(0, B);
if (Q !== 0)
throw new Error(`Failed to create key encoder: ${Q}`);
const g = new DataView(this.exports.memory.buffer);
this.encoder = g.getUint32(B, !0), this.exports.ghostty_wasm_free_opaque(B);
}
/**
* Set an encoder option
*/
setOption(A, B) {
const Q = this.exports.ghostty_wasm_alloc_u8(), g = new DataView(this.exports.memory.buffer);
typeof B == "boolean" ? g.setUint8(Q, B ? 1 : 0) : g.setUint8(Q, B);
const C = this.exports.ghostty_key_encoder_setopt(this.encoder, A, Q);
if (this.exports.ghostty_wasm_free_u8(Q), C !== void 0 && C !== 0)
throw new Error(`Failed to set encoder option: ${C}`);
}
/**
* Enable Kitty keyboard protocol with specified flags
*/
setKittyFlags(A) {
this.setOption(m.KITTY_KEYBOARD_FLAGS, A);
}
/**
* Encode a key event to escape sequence
*/
encode(A) {
const B = this.exports.ghostty_wasm_alloc_opaque(), Q = this.exports.ghostty_key_event_new(0, B);
if (Q !== 0)
throw new Error(`Failed to create key event: ${Q}`);
const g = new DataView(this.exports.memory.buffer), C = g.getUint32(B, !0);
if (this.exports.ghostty_wasm_free_opaque(B), this.exports.ghostty_key_event_set_action(C, A.action), this.exports.ghostty_key_event_set_key(C, A.key), this.exports.ghostty_key_event_set_mods(C, A.mods), A.utf8) {
const J = new TextEncoder().encode(A.utf8), s = this.exports.ghostty_wasm_alloc_u8_array(J.length);
new Uint8Array(this.exports.memory.buffer).set(J, s), this.exports.ghostty_key_event_set_utf8(C, s, J.length), this.exports.ghostty_wasm_free_u8_array(s, J.length);
}
const I = 32, w = this.exports.ghostty_wasm_alloc_u8_array(I), o = this.exports.ghostty_wasm_alloc_usize(), i = this.exports.ghostty_key_encoder_encode(
this.encoder,
C,
w,
I,
o
);
if (i !== 0)
throw this.exports.ghostty_wasm_free_u8_array(w, I), this.exports.ghostty_wasm_free_usize(o), this.exports.ghostty_key_event_free(C), new Error(`Failed to encode key: ${i}`);
const M = g.getUint32(o, !0), k = new Uint8Array(this.exports.memory.buffer, w, M).slice();
return this.exports.ghostty_wasm_free_u8_array(w, I), this.exports.ghostty_wasm_free_usize(o), this.exports.ghostty_key_event_free(C), k;
}
/**
* Free encoder resources
*/
dispose() {
this.encoder && (this.exports.ghostty_key_encoder_free(this.encoder), this.encoder = 0);
}
}
const f = class Y {
/**
* Create a new terminal.
*
* @param exports WASM exports
* @param memory WASM memory
* @param cols Number of columns (default: 80)
* @param rows Number of rows (default: 24)
* @param config Optional terminal configuration (colors, scrollback)
* @throws Error if allocation fails
*/
constructor(A, B, Q = 80, g = 24, C) {
var w;
this.exports = A, this.memory = B, this._cols = Q, this._rows = g;
let I;
if (C) {
const o = this.exports.ghostty_wasm_alloc_u8_array(d);
if (o === 0)
throw new Error("Failed to allocate config (out of memory)");
try {
const i = new DataView(this.memory.buffer);
let M = o;
i.setUint32(M, C.scrollbackLimit ?? 1e4, !0), M += 4, i.setUint32(M, C.fgColor ?? 0, !0), M += 4, i.setUint32(M, C.bgColor ?? 0, !0), M += 4, i.setUint32(M, C.cursorColor ?? 0, !0), M += 4;
for (let k = 0; k < 16; k++) {
const G = ((w = C.palette) == null ? void 0 : w[k]) ?? 0;
i.setUint32(M, G, !0), M += 4;
}
I = this.exports.ghostty_terminal_new_with_config(Q, g, o);
} finally {
this.exports.ghostty_wasm_free_u8_array(o, d);
}
} else
I = this.exports.ghostty_terminal_new(Q, g);
if (I === 0)
throw new Error("Failed to allocate terminal (out of memory)");
this.handle = I;
}
/**
* Free the terminal. Must be called to prevent memory leaks.
*/
free() {
this.handle !== 0 && (this.exports.ghostty_terminal_free(this.handle), this.handle = 0);
}
/**
* Write data to terminal (parses VT sequences and updates screen).
*
* @param data UTF-8 string or Uint8Array
*
* @example
* ```typescript
* term.write('Hello, World!\n');
* term.write('\x1b[1;31mBold Red\x1b[0m\n');
* term.write(new Uint8Array([0x1b, 0x5b, 0x41])); // Up arrow
* ```
*/
write(A) {
const B = typeof A == "string" ? new TextEncoder().encode(A) : A;
if (B.length === 0)
return;
const Q = this.exports.ghostty_wasm_alloc_u8_array(B.length);
new Uint8Array(this.memory.buffer).set(B, Q);
try {
this.exports.ghostty_terminal_write(this.handle, Q, B.length);
} finally {
this.exports.ghostty_wasm_free_u8_array(Q, B.length);
}
}
/**
* Resize the terminal.
*
* @param cols New column count
* @param rows New row count
*/
resize(A, B) {
this.exports.ghostty_terminal_resize(this.handle, A, B), this._cols = A, this._rows = B;
}
/**
* Get terminal dimensions.
*/
get cols() {
return this._cols;
}
get rows() {
return this._rows;
}
/**
* Get terminal dimensions (for IRenderable compatibility)
*/
getDimensions() {
return { cols: this._cols, rows: this._rows };
}
/**
* Get cursor position and visibility.
*/
getCursor() {
return {
x: this.exports.ghostty_terminal_get_cursor_x(this.handle),
y: this.exports.ghostty_terminal_get_cursor_y(this.handle),
visible: this.exports.ghostty_terminal_get_cursor_visible(this.handle)
};
}
/**
* Get scrollback length (number of lines in history).
*/
getScrollbackLength() {
return this.exports.ghostty_terminal_get_scrollback_length(this.handle);
}
/**
* Check if terminal is in alternate screen buffer mode.
*
* The alternate screen is used by vim, less, htop, etc.
* When active, normal buffer is preserved and restored when the app exits.
*
* @returns true if in alternate screen, false if in normal screen
*
* @example
* ```typescript
* // Detect if vim is running
* if (term.isAlternateScreen()) {
* console.log('Full-screen app is active');
* }
* ```
*/
isAlternateScreen() {
return !!this.exports.ghostty_terminal_is_alternate_screen(this.handle);
}
/**
* Check if a row is wrapped from the previous row.
*
* Wrapped rows are continuations of long lines that exceeded terminal width.
* Used for text selection to treat wrapped lines as single logical lines.
*
* @param row Row index (0 = top visible line)
* @returns true if row continues from previous line, false otherwise
*
* @example
* ```typescript
* // Get full logical line including wraps
* let text = '';
* for (let row = 0; row < term.rows; row++) {
* const line = term.getLine(row);
* text += lineToString(line);
*
* // Only add newline if NOT wrapped
* if (!term.isRowWrapped(row + 1)) {
* text += '\n';
* }
* }
* ```
*/
isRowWrapped(A) {
return A < 0 || A >= this._rows ? !1 : !!this.exports.ghostty_terminal_is_row_wrapped(this.handle, A);
}
/**
* Get a line of cells from the visible screen.
*
* @param y Line number (0 = top visible line)
* @returns Array of cells, or null if y is out of bounds
*
* @example
* ```typescript
* const cells = term.getLine(0);
* if (cells) {
* for (const cell of cells) {
* const char = String.fromCodePoint(cell.codepoint);
* const isBold = (cell.flags & CellFlags.BOLD) !== 0;
* console.log(`"${char}" ${isBold ? 'bold' : 'normal'}`);
* }
* }
* ```
*/
getLine(A) {
if (A < 0 || A >= this._rows)
return null;
const B = this._cols * Y.CELL_SIZE, Q = this.exports.ghostty_wasm_alloc_u8_array(B);
try {
const g = this.exports.ghostty_terminal_get_line(this.handle, A, Q, this._cols);
if (g < 0)
return null;
const C = [], I = new DataView(this.memory.buffer, Q, B);
for (let w = 0; w < g; w++) {
const o = w * Y.CELL_SIZE;
C.push({
codepoint: I.getUint32(o, !0),
fg_r: I.getUint8(o + 4),
fg_g: I.getUint8(o + 5),
fg_b: I.getUint8(o + 6),
bg_r: I.getUint8(o + 7),
bg_g: I.getUint8(o + 8),
bg_b: I.getUint8(o + 9),
flags: I.getUint8(o + 10),
width: I.getUint8(o + 11),
hyperlink_id: I.getUint16(o + 12, !0)
});
}
return C;
} finally {
this.exports.ghostty_wasm_free_u8_array(Q, B);
}
}
/**
* Get a line from scrollback history.
*
* @param offset Line offset from top of scrollback (0 = oldest line)
* @returns Array of cells, or null if not available
*/
getScrollbackLine(A) {
const B = this.getScrollbackLength();
if (A < 0 || A >= B)
return null;
const Q = this._cols * Y.CELL_SIZE, g = this.exports.ghostty_wasm_alloc_u8_array(Q);
try {
const C = this.exports.ghostty_terminal_get_scrollback_line(
this.handle,
A,
g,
this._cols
);
if (C < 0)
return null;
const I = [], w = new DataView(this.memory.buffer, g, Q);
for (let o = 0; o < C; o++) {
const i = o * Y.CELL_SIZE;
I.push({
codepoint: w.getUint32(i, !0),
fg_r: w.getUint8(i + 4),
fg_g: w.getUint8(i + 5),
fg_b: w.getUint8(i + 6),
bg_r: w.getUint8(i + 7),
bg_g: w.getUint8(i + 8),
bg_b: w.getUint8(i + 9),
flags: w.getUint8(i + 10),
width: w.getUint8(i + 11),
hyperlink_id: w.getUint16(i + 12, !0)
});
}
return I;
} finally {
this.exports.ghostty_wasm_free_u8_array(g, Q);
}
}
/**
* Check if any part of the screen is dirty.
*/
isDirty() {
return this.exports.ghostty_terminal_is_dirty(this.handle);
}
/**
* Check if a specific row is dirty.
*/
isRowDirty(A) {
return A < 0 || A >= this._rows ? !1 : this.exports.ghostty_terminal_is_row_dirty(this.handle, A);
}
/**
* Clear all dirty flags (call after rendering).
*/
clearDirty() {
this.exports.ghostty_terminal_clear_dirty(this.handle);
}
/**
* Get all visible lines at once (convenience method).
*
* @returns Array of line arrays, or empty array on error
*/
getAllLines() {
const A = [];
for (let B = 0; B < this._rows; B++) {
const Q = this.getLine(B);
Q && A.push(Q);
}
return A;
}
/**
* Get only the dirty lines (for optimized rendering).
*
* @returns Map of row number to cell array
*/
getDirtyLines() {
const A = /* @__PURE__ */ new Map();
for (let B = 0; B < this._rows; B++)
if (this.isRowDirty(B)) {
const Q = this.getLine(B);
Q && A.set(B, Q);
}
return A;
}
/**
* Get hyperlink URI by ID
*
* @param hyperlinkId Hyperlink ID from a GhosttyCell (0 = no link)
* @returns URI string or null if ID is invalid/not found
*/
getHyperlinkUri(A) {
if (A === 0)
return null;
const B = 2048, Q = this.exports.ghostty_wasm_alloc_u8_array(B);
try {
const g = this.exports.ghostty_terminal_get_hyperlink_uri(
this.handle,
A,
Q,
B
);
if (g === 0)
return null;
const C = new Uint8Array(this.memory.buffer, Q, g);
return new TextDecoder().decode(C);
} finally {
this.exports.ghostty_wasm_free_u8_array(Q, B);
}
}
// ============================================================================
// Terminal Modes
// ============================================================================
/**
* Query terminal mode state
*/
getMode(A, B = !1) {
return this.exports.ghostty_terminal_get_mode(this.handle, A, B ? 1 : 0) !== 0;
}
/**
* Check if bracketed paste mode is enabled
*/
hasBracketedPaste() {
return this.exports.ghostty_terminal_has_bracketed_paste(this.handle) !== 0;
}
/**
* Check if focus event reporting is enabled
*/
hasFocusEvents() {
return this.exports.ghostty_terminal_has_focus_events(this.handle) !== 0;
}
/**
* Check if mouse tracking is enabled
*/
hasMouseTracking() {
return this.exports.ghostty_terminal_has_mouse_tracking(this.handle) !== 0;
}
};
f.CELL_SIZE = 16;
let j = f;
class K {
constructor() {
this.listeners = [], this.event = (A) => (this.listeners.push(A), {
dispose: () => {
const B = this.listeners.indexOf(A);
B >= 0 && this.listeners.splice(B, 1);
}
});
}
fire(A) {
for (const B of this.listeners)
B(A);
}
dispose() {
this.listeners = [];
}
}
class V {
constructor(A) {
this.bufferChangeEmitter = new K(), this.terminal = A;
}
get active() {
const A = this.terminal.wasmTerm;
return A ? A.isAlternateScreen() ? this.alternate : this.normal : this.normal;
}
get normal() {
return this._normalBuffer || (this._normalBuffer = new n(this.terminal, "normal")), this._normalBuffer;
}
get alternate() {
return this._alternateBuffer || (this._alternateBuffer = new n(this.terminal, "alternate")), this._alternateBuffer;
}
get onBufferChange() {
return this.bufferChangeEmitter.event;
}
/**
* Internal: Fire buffer change event when screen switches
* Should be called by Terminal when detecting screen change
*/
_fireBufferChange(A) {
this.bufferChangeEmitter.fire(A);
}
}
class n {
constructor(A, B) {
this.terminal = A, this.bufferType = B;
const Q = {
codepoint: 0,
fg_r: 204,
fg_g: 204,
fg_b: 204,
bg_r: 0,
bg_g: 0,
bg_b: 0,
flags: 0,
width: 1,
hyperlink_id: 0
};
this.nullCell = new S(Q, 0);
}
get type() {
return this.bufferType;
}
get cursorX() {
const A = this.getWasmTerm();
return A ? A.getCursor().x : 0;
}
get cursorY() {
const A = this.getWasmTerm();
return A ? A.getCursor().y : 0;
}
get viewportY() {
return 0;
}
get baseY() {
return 0;
}
get length() {
const A = this.getWasmTerm();
return A ? this.bufferType === "alternate" ? A.rows : A.getScrollbackLength() + A.rows : 0;
}
getLine(A) {
const B = this.getWasmTerm();
if (!B || A < 0 || A >= this.length)
return;
const Q = B.getScrollbackLength();
let g, C, I;
if (this.bufferType === "normal" && A < Q) {
const w = A;
g = B.getScrollbackLine(w), I = !1;
} else
C = this.bufferType === "normal" ? A - Q : A, g = B.getLine(C), I = B.isRowWrapped(C);
if (g)
return new W(g, I, B.cols);
}
getNullCell() {
return this.nullCell;
}
getWasmTerm() {
return this.terminal.wasmTerm;
}
}
class W {
constructor(A, B, Q) {
this.cells = A, this._isWrapped = B, this._length = Q;
}
get length() {
return this._length;
}
get isWrapped() {
return this._isWrapped;
}
getCell(A) {
if (!(A < 0 || A >= this._length))
return A >= this.cells.length ? new S(
{
codepoint: 0,
fg_r: 204,
fg_g: 204,
fg_b: 204,
bg_r: 0,
bg_g: 0,
bg_b: 0,
flags: 0,
width: 1,
hyperlink_id: 0
},
A
) : new S(this.cells[A], A);
}
translateToString(A = !1, B = 0, Q = this._length) {
const g = Math.max(0, Math.min(B, this._length)), C = Math.max(g, Math.min(Q, this._length));
let I = "";
for (let w = g; w < C; w++) {
const o = this.getCell(w);
if (o) {
const i = o.getChars();
I += i;
}
}
return A && (I = I.trimEnd()), I;
}
}
class S {
constructor(A, B) {
this.cell = A, this.x = B;
}
getChars() {
const A = this.cell.codepoint;
return A === 0 ? "" : A < 0 || A > 1114111 || A >= 55296 && A <= 57343 ? "�" : String.fromCodePoint(A);
}
getCode() {
return this.cell.codepoint;
}
getWidth() {
return this.cell.width;
}
getFgColorMode() {
return -1;
}
getBgColorMode() {
return -1;
}
getFgColor() {
return this.cell.fg_r << 16 | this.cell.fg_g << 8 | this.cell.fg_b;
}
getBgColor() {
return this.cell.bg_r << 16 | this.cell.bg_g << 8 | this.cell.bg_b;
}
isBold() {
return this.cell.flags & U.BOLD ? 1 : 0;
}
isItalic() {
return this.cell.flags & U.ITALIC ? 1 : 0;
}
isUnderline() {
return this.cell.flags & U.UNDERLINE ? 1 : 0;
}
isStrikethrough() {
return this.cell.flags & U.STRIKETHROUGH ? 1 : 0;
}
isBlink() {
return this.cell.flags & U.BLINK ? 1 : 0;
}
isInverse() {
return this.cell.flags & U.INVERSE ? 1 : 0;
}
isInvisible() {
return this.cell.flags & U.INVISIBLE ? 1 : 0;
}
isFaint() {
return this.cell.flags & U.FAINT ? 1 : 0;
}
/**
* Get hyperlink ID for this cell (0 = no link)
* Used by link detection system
*/
getHyperlinkId() {
return this.cell.hyperlink_id;
}
/**
* Get the Unicode codepoint for this cell
* Used by link detection system
*/
getCodepoint() {
return this.cell.codepoint;
}
/**
* Check if cell has dim/faint attribute
* Added for IBufferCell compatibility
*/
isDim() {
return (this.cell.flags & U.FAINT) !== 0;
}
}
const Z = {
// Letters
KeyA: D.A,
KeyB: D.B,
KeyC: D.C,
KeyD: D.D,
KeyE: D.E,
KeyF: D.F,
KeyG: D.G,
KeyH: D.H,
KeyI: D.I,
KeyJ: D.J,
KeyK: D.K,
KeyL: D.L,
KeyM: D.M,
KeyN: D.N,
KeyO: D.O,
KeyP: D.P,
KeyQ: D.Q,
KeyR: D.R,
KeyS: D.S,
KeyT: D.T,
KeyU: D.U,
KeyV: D.V,
KeyW: D.W,
KeyX: D.X,
KeyY: D.Y,
KeyZ: D.Z,
// Numbers
Digit1: D.ONE,
Digit2: D.TWO,
Digit3: D.THREE,
Digit4: D.FOUR,
Digit5: D.FIVE,
Digit6: D.SIX,
Digit7: D.SEVEN,
Digit8: D.EIGHT,
Digit9: D.NINE,
Digit0: D.ZERO,
// Special keys
Enter: D.ENTER,
Escape: D.ESCAPE,
Backspace: D.BACKSPACE,
Tab: D.TAB,
Space: D.SPACE,
// Punctuation
Minus: D.MINUS,
Equal: D.EQUAL,
BracketLeft: D.BRACKET_LEFT,
BracketRight: D.BRACKET_RIGHT,
Backslash: D.BACKSLASH,
Semicolon: D.SEMICOLON,
Quote: D.QUOTE,
Backquote: D.GRAVE,
Comma: D.COMMA,
Period: D.PERIOD,
Slash: D.SLASH,
// Function keys
CapsLock: D.CAPS_LOCK,
F1: D.F1,
F2: D.F2,
F3: D.F3,
F4: D.F4,
F5: D.F5,
F6: D.F6,
F7: D.F7,
F8: D.F8,
F9: D.F9,
F10: D.F10,
F11: D.F11,
F12: D.F12,
// Special function keys
PrintScreen: D.PRINT_SCREEN,
ScrollLock: D.SCROLL_LOCK,
Pause: D.PAUSE,
Insert: D.INSERT,
Home: D.HOME,
PageUp: D.PAGE_UP,
Delete: D.DELETE,
End: D.END,
PageDown: D.PAGE_DOWN,
// Arrow keys
ArrowRight: D.RIGHT,
ArrowLeft: D.LEFT,
ArrowDown: D.DOWN,
ArrowUp: D.UP,
// Keypad
NumLock: D.NUM_LOCK,
NumpadDivide: D.KP_DIVIDE,
NumpadMultiply: D.KP_MULTIPLY,
NumpadSubtract: D.KP_MINUS,
NumpadAdd: D.KP_PLUS,
NumpadEnter: D.KP_ENTER,
Numpad1: D.KP_1,
Numpad2: D.KP_2,
Numpad3: D.KP_3,
Numpad4: D.KP_4,
Numpad5: D.KP_5,
Numpad6: D.KP_6,
Numpad7: D.KP_7,
Numpad8: D.KP_8,
Numpad9: D.KP_9,
Numpad0: D.KP_0,
NumpadDecimal: D.KP_PERIOD,
// International
IntlBackslash: D.NON_US_BACKSLASH,
ContextMenu: D.APPLICATION,
// Additional function keys
F13: D.F13,
F14: D.F14,
F15: D.F15,
F16: D.F16,
F17: D.F17,
F18: D.F18,
F19: D.F19,
F20: D.F20,
F21: D.F21,
F22: D.F22,
F23: D.F23,
F24: D.F24
};
class X {
/**
* Create a new InputHandler
* @param ghostty - Ghostty instance (for creating KeyEncoder)
* @param container - DOM element to attach listeners to
* @param onData - Callback for terminal data (escape sequences to send to PTY)
* @param onBell - Callback for bell/beep event
* @param onKey - Optional callback for raw key events
* @param customKeyEventHandler - Optional custom key event handler
*/
constructor(A, B, Q, g, C, I) {
this.keydownListener = null, this.keypressListener = null, this.pasteListener = null, this.isDisposed = !1, this.encoder = A.createKeyEncoder(), this.container = B, this.onDataCallback = Q, this.onBellCallback = g, this.onKeyCallback = C, this.customKeyEventHandler = I, this.attach();
}
/**
* Set custom key event handler (for runtime updates)
*/
setCustomKeyEventHandler(A) {
this.customKeyEventHandler = A;
}
/**
* Attach keyboard event listeners to container
*/
attach() {
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);
}
/**
* Map KeyboardEvent.code to USB HID Key enum value
* @param code - KeyboardEvent.code value
* @returns Key enum value or null if unmapped
*/
mapKeyCode(A) {
return Z[A] ?? null;
}
/**
* Extract modifier flags from KeyboardEvent
* @param event - KeyboardEvent
* @returns Mods flags
*/
extractModifiers(A) {
let B = q.NONE;
return A.shiftKey && (B |= q.SHIFT), A.ctrlKey && (B |= q.CTRL), A.altKey && (B |= q.ALT), A.metaKey && (B |= q.SUPER), B;
}
/**
* Check if this is a printable character with no special modifiers
* @param event - KeyboardEvent
* @returns true if printable character
*/
isPrintableCharacter(A) {
return A.ctrlKey && !A.altKey || A.altKey && !A.ctrlKey || A.metaKey ? !1 : A.key.length === 1;
}
/**
* Handle keydown event
* @param event - KeyboardEvent
*/
handleKeyDown(A) {
if (this.isDisposed)
return;
if (this.onKeyCallback && this.onKeyCallback({ key: A.key, domEvent: A }), this.customKeyEventHandler && this.customKeyEventHandler(A)) {
A.preventDefault();
return;
}
if ((A.ctrlKey || A.metaKey) && A.code === "KeyV" || A.metaKey && A.code === "KeyC")
return;
if (this.isPrintableCharacter(A)) {
A.preventDefault(), this.onDataCallback(A.key);
return;
}
const B = this.mapKeyCode(A.code);
if (B === null)
return;
const Q = this.extractModifiers(A);
if (Q === q.NONE || Q === q.SHIFT) {
let C = null;
switch (B) {
case D.ENTER:
C = "\r";
break;
case D.TAB:
C = " ";
break;
case D.BACKSPACE:
C = "";
break;
case D.ESCAPE:
C = "\x1B";
break;
case D.UP:
C = "\x1B[A";
break;
case D.DOWN:
C = "\x1B[B";
break;
case D.RIGHT:
C = "\x1B[C";
break;
case D.LEFT:
C = "\x1B[D";
break;
case D.HOME:
C = "\x1B[H";
break;
case D.END:
C = "\x1B[F";
break;
case D.INSERT:
C = "\x1B[2~";
break;
case D.DELETE:
C = "\x1B[3~";
break;
case D.PAGE_UP:
C = "\x1B[5~";
break;
case D.PAGE_DOWN:
C = "\x1B[6~";
break;
case D.F1:
C = "\x1BOP";
break;
case D.F2:
C = "\x1BOQ";
break;
case D.F3:
C = "\x1BOR";
break;
case D.F4:
C = "\x1BOS";
break;
case D.F5:
C = "\x1B[15~";
break;
case D.F6:
C = "\x1B[17~";
break;
case D.F7:
C = "\x1B[18~";
break;
case D.F8:
C = "\x1B[19~";
break;
case D.F9:
C = "\x1B[20~";
break;
case D.F10:
C = "\x1B[21~";
break;
case D.F11:
C = "\x1B[23~";
break;
case D.F12:
C = "\x1B[24~";
break;
}
if (C !== null) {
A.preventDefault(), this.onDataCallback(C);
return;
}
}
const g = b.PRESS;
try {
const C = A.key.length === 1 && A.key.charCodeAt(0) < 128 ? A.key.toLowerCase() : void 0, I = this.encoder.encode({
action: g,
key: B,
mods: Q,
utf8: C
}), o = new TextDecoder().decode(I);
A.preventDefault(), A.stopPropagation(), o.length > 0 && this.onDataCallback(o);
} catch (C) {
console.warn("Failed to encode key:", A.code, C);
}
}
/**
* Handle paste event from clipboard
* @param event - ClipboardEvent
*/
handlePaste(A) {
if (this.isDisposed)
return;
A.preventDefault(), A.stopPropagation();
const B = A.clipboardData;
if (!B) {
console.warn("No clipboard data available");
return;
}
const Q = B.getData("text/plain");
if (!Q) {
console.warn("No text in clipboard");
return;
}
this.onDataCallback(Q);
}
/**
* Dispose the InputHandler and remove event listeners
*/
dispose() {
this.isDisposed || (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.pasteListener = null), this.isDisposed = !0);
}
/**
* Check if handler is disposed
*/
isActive() {
return !this.isDisposed;
}
}
class P {
// Terminal instance for buffer access
constructor(A) {
this.terminal = A, this.providers = [], this.linkCache = /* @__PURE__ */ new Map(), this.scannedRows = /* @__PURE__ */ new Set();
}
/**
* Register a link provider
*/
registerProvider(A) {
this.providers.push(A), this.invalidateCache();
}
/**
* Get link at the specified buffer position
* @param col Column (0-based)
* @param row Absolute row in buffer (0-based)
* @returns Link at position, or undefined if none
*/
async getLinkAt(A, B) {
const Q = this.terminal.buffer.active.getLine(B);
if (!Q || A < 0 || A >= Q.length)
return;
const g = Q.getCell(A);
if (!g)
return;
const C = g.getHyperlinkId();
if (C > 0) {
const I = `h${C}`;
if (this.linkCache.has(I))
return this.linkCache.get(I);
}
if (this.scannedRows.has(B) || await this.scanRow(B), C > 0) {
const I = `h${C}`, w = this.linkCache.get(I);
if (w)
return w;
}
for (const I of this.linkCache.values())
if (this.isPositionInLink(A, B, I))
return I;
}
/**
* Scan a row for links using all registered providers
*/
async scanRow(A) {
this.scannedRows.add(A);
const B = [];
for (const Q of this.providers) {
const g = await new Promise((C) => {
Q.provideLinks(A, C);
});
g && B.push(...g);
}
for (const Q of B)
this.cacheLink(Q);
}
/**
* Cache a link for fast lookup
*/
cacheLink(A) {
const { start: B } = A.range, Q = this.terminal.buffer.active.getLine(B.y);
if (Q) {
const w = Q.getCell(B.x);
if (!w) {
const { start: i, end: M } = A.range, k = `r${i.y}:${i.x}-${M.x}`;
this.linkCache.set(k, A);
return;
}
const o = w.getHyperlinkId();
if (o > 0) {
this.linkCache.set(`h${o}`, A);
return;
}
}
const { start: g, end: C } = A.range, I = `r${g.y}:${g.x}-${C.x}`;
this.linkCache.set(I, A);
}
/**
* Check if a position is within a link's range
*/
isPositionInLink(A, B, Q) {
const { start: g, end: C } = Q.range;
return B < g.y || B > C.y ? !1 : g.y === C.y ? A >= g.x && A <= C.x : B === g.y ? A >= g.x : B === C.y ? A <= C.x : !0;
}
/**
* Invalidate cache when terminal content changes
* Should be called on terminal write, resize, or clear
*/
invalidateCache() {
this.linkCache.clear(), this.scannedRows.clear();
}
/**
* Invalidate cache for specific rows
* Used when only part of the terminal changed
*/
invalidateRows(A, B) {
for (let g = A; g <= B; g++)
this.scannedRows.delete(g);
const Q = [];
for (const [g, C] of this.linkCache.entries()) {
const { start: I, end: w } = C.range;
(I.y >= A && I.y <= B || w.y >= A && w.y <= B || I.y < A && w.y > B) && Q.push(g);
}
for (const g of Q)
this.linkCache.delete(g);
}
/**
* Dispose and cleanup
*/
dispose() {
var A;
this.linkCache.clear(), this.scannedRows.clear();
for (const B of this.providers)
(A = B.dispose) == null || A.call(B);
this.providers = [];
}
}
class v {
constructor(A) {
this.terminal = A;
}
/**
* Provide all OSC 8 links on the given row
* Note: This may return links that span multiple rows
*/
provideLinks(A, B) {
const Q = [], g = /* @__PURE__ */ new Set(), C = this.terminal.buffer.active.getLine(A);
if (!C) {
B(void 0);
return;
}
for (let I = 0; I < C.length; I++) {
const w = C.getCell(I);
if (!w)
continue;
const o = w.getHyperlinkId();
if (o === 0 || g.has(o))
continue;
g.add(o);
const i = this.findLinkRange(o, A, I);
if (!this.terminal.wasmTerm)
continue;
const M = this.terminal.wasmTerm.getHyperlinkUri(o);
M && Q.push({
text: M,
range: i,
activate: (k) => {
(k.ctrlKey || k.metaKey) && window.open(M, "_blank", "noopener,noreferrer");
}
});
}
B(Q.length > 0 ? Q : void 0);
}
/**
* Find the full extent of a link by scanning for contiguous cells
* with the same hyperlink_id. Handles multi-line links.
*/
findLinkRange(A, B, Q) {
const g = this.terminal.buffer.active;
let C = B, I = Q;
for (; I > 0; ) {
const M = g.getLine(C);
if (!M)
break;
const k = M.getCell(I - 1);
if (!k || k.getHyperlinkId() !== A)
break;
I--;
}
if (I === 0 && C > 0) {
let M = C - 1;
for (; M >= 0; ) {
const k = g.getLine(M);
if (!k || k.length === 0)
break;
const G = k.getCell(k.length - 1);
if (!G || G.getHyperlinkId() !== A)
break;
C = M, I = 0;
for (let J = k.length - 1; J >= 0; J--) {
const s = k.getCell(J);
if (!s || s.getHyperlinkId() !== A) {
I = J + 1;
break;
}
}
if (I === 0)
M--;
else
break;
}
}
let w = B, o = Q;
const i = g.getLine(w);
if (i) {
for (; o < i.length - 1; ) {
const M = i.getCell(o + 1);
if (!M || M.getHyperlinkId() !== A)
break;
o++;
}
if (o === i.length - 1) {
let M = w + 1;
const k = g.length;
for (; M < k; ) {
const G = g.getLine(M);
if (!G || G.length === 0)
break;
const J = G.getCell(0);
if (!J || J.getHyperlinkId() !== A)
break;
w = M, o = 0;
for (let s = 0; s < G.length; s++) {
const F = G.getCell(s);
if (!F)
break;
if (F.getHyperlinkId() !== A) {
o = s - 1;
break;
}
o = s;
}
if (o === G.length - 1)
M++;
else
break;
}
}
}
return {
start: { x: I, y: C },
end: { x: o, y: w }
};
}
dispose() {
}
}
const e = class H {
constructor(A) {
this.terminal = A;
}
/**
* Provide all regex-detected URLs on the given row
*/
provideLinks(A, B) {
const Q = [], g = this.terminal.buffer.active.getLine(A);
if (!g) {
B(void 0);
return;
}
const C = this.lineToText(g);
H.URL_REGEX.lastIndex = 0;
let I = H.URL_REGEX.exec(C);
for (; I !== null; ) {
let w = I[0];
const o = I.index;
let i = I.index + w.length - 1;
const M = w.replace(H.TRAILING_PUNCTUATION, "");
M.length < w.length && (w = M, i = o + w.length - 1), w.length > 8 && Q.push({
text: w,
range: {
start: { x: o, y: A },
end: { x: i, y: A }
},
activate: (k) => {
(k.ctrlKey || k.metaKey) && window.open(w, "_blank", "noopener,noreferrer");
}
}), I = H.URL_REGEX.exec(C);
}
B(Q.length > 0 ? Q : void 0);
}
/**
* Convert a buffer line to plain text string
*/
lineToText(A) {
const B = [];
for (let Q = 0; Q < A.length; Q++) {
const g = A.getCell(Q);
if (!g) {
B.push(" ");
continue;
}
const C = g.getCodepoint();
C === 0 || C < 32 ? B.push(" ") : B.push(String.fromCodePoint(C));
}
return B.join("");
}
dispose() {
}
};
e.URL_REGEX = /(?:https?:\/\/|mailto:|ftp:\/\/|ssh:\/\/|git:\/\/|tel:|magnet:|gemini:\/\/|gopher:\/\/|news:)[\w\-.~:\/?#@!$&*+,;=%]+/gi;
e.TRAILING_PUNCTUATION = /[.,;!?)\]]+$/;
let u = e;
const r = {
foreground: "#d4d4d4",
background: "#1e1e1e",
cursor: "#ffffff",
cursorAccent: "#1e1e1e",
selectionBackground: "rgba(255, 255, 255, 0.3)",
selectionForeground: "#d4d4d4",
black: "#000000",
red: "#cd3131",
green: "#0dbc79",
yellow: "#e5e510",
blue: "#2472c8",
magenta: "#bc3fbc",
cyan: "#11a8cd",
white: "#e5e5e5",
brightBlack: "#666666",
brightRed: "#f14c4c",
brightGreen: "#23d18b",
brightYellow: "#f5f543",
brightBlue: "#3b8eea",
brightMagenta: "#d670d6",
brightCyan: "#29b8db",
brightWhite: "#ffffff"
};
class _ {
constructor(A, B = {}) {
this.cursorVisible = !0, this.lastCursorPosition = { x: 0, y: 0 }, this.lastViewportY = 0, this.hoveredHyperlinkId = 0, this.previousHoveredHyperlinkId = 0, this.hoveredLinkRange = null, this.previousHoveredLinkRange = null, this.canvas = A;
const Q = A.getContext("2d", { alpha: !1 });
if (!Q)
throw new Error("Failed to get 2D rendering context");
this.ctx = Q, this.fontSize = B.fontSize ?? 15, this.fontFamily = B.fontFamily ?? "monospace", this.cursorStyle = B.cursorStyle ?? "block", this.cursorBlink = B.cursorBlink ?? !1, this.theme = { ...r, ...B.theme }, this.devicePixelRatio = B.devicePixelRatio ?? window.devicePixelRatio ?? 1, this.palette = [
this.theme.black,
this.theme.red,
this.theme.green,
this.theme.yellow,
this.theme.blue,
this.theme.magenta,
this.theme.cyan,
this.theme.white,
this.theme.brightBlack,
this.theme.brightRed,
this.theme.brightGreen,
this.theme.brightYellow,
this.theme.brightBlue,
this.theme.brightMagenta,
this.theme.brightCyan,
this.theme.brightWhite
], this.metrics = this.measureFont(), this.cursorBlink && this.startCursorBlink();
}
// ==========================================================================
// Font Metrics Measurement
// ==========================================================================
measureFont() {
const B = document.createElement("canvas").getContext("2d");
B.font = `${this.fontSize}px ${this.fontFamily}`;
const Q = B.measureText("M"), g = Math.ceil(Q.width), C = Q.actualBoundingBoxAscent || this.fontSize * 0.8, I = Q.actualBoundingBoxDescent || this.fontSize * 0.2, w = Math.ceil(C + I) + 2, o = Math.ceil(C) + 1;
return { width: g, height: w, baseline: o };
}
/**
* Remeasure font metrics (call after font loads or changes)
*/
remeasureFont() {
this.metrics = this.measureFont();
}
// ==========================================================================
// Color Conversion
// ==========================================================================
rgbToCSS(A, B, Q) {
return `rgb(${A}, ${B}, ${Q})`;
}
// ==========================================================================
// Canvas Sizing
// ==========================================================================
/**
* Resize canvas to fit terminal dimensions
*/
resize(A, B) {
const Q = A * this.metrics.width, g = B * this.metrics.height;
this.canvas.style.width = `${Q}px`, this.canvas.style.height = `${g}px`, this.canvas.width = Q * this.devicePixelRatio, this.canvas.height = g * 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, Q, g);
}
// ==========================================================================
// Main Rendering
// ==========================================================================
/**
* Render the terminal buffer to canvas
*/
render(A, B = !1, Q = 0, g, C = 1) {
const I = A.getCursor(), w = A.getDimensions(), o = g ? g.getScrollbackLength() : 0;
(this.canvas.width !== w.cols * this.metrics.width * this.devicePixelRatio || this.canvas.height !== w.rows * this.metrics.height * this.devicePixelRatio) && (this.resize(w.cols, w.rows), B = !0), Q !== this.lastViewportY && (B = !0, this.lastViewportY = Q);
const M = I.x !== this.lastCursorPosition.x || I.y !== this.lastCursorPosition.y;
if (M || this.cursorBlink) {
if (!B && !A.isRowDirty(I.y)) {
const N = A.getLine(I.y);
N && this.renderLine(N, I.y, w.cols);
}
if (M && this.lastCursorPosition.y !== I.y && !B && !A.isRowDirty(this.lastCursorPosition.y)) {
const N = A.getLine(this.lastCursorPosition.y);
N && this.renderLine(N, this.lastCursorPosition.y, w.cols);
}
}
const k = this.selectionManager && this.selectionManager.hasSelection(), G = /* @__PURE__ */ new Set();
if (k) {
const N = this.selectionManager.getSelectionCoords();
if (N)
for (let c = N.startRow; c <= N.endRow; c++)
G.add(c);
}
if (this.selectionManager) {
const N = this.selectionManager.getDirtySelectionRows();
if (N.size > 0) {
for (const c of N)
G.add(c);
this.selectionManager.clearDirtySelectionRows();
}
}
const J = /* @__PURE__ */ new Set(), s = this.hoveredHyperlinkId !== this.previousHoveredHyperlinkId, F = JSON.stringify(this.hoveredLinkRange) !== JSON.stringify(this.previousHoveredLinkRange);
if (s) {
for (let N = 0; N < w.rows; N++) {
let c = null;
if (Q > 0)
if (N < Q && g) {
const h = o - Math.floor(Q) + N;
c = g.getScrollbackLine(h);
} else {
const h = N - Math.floor(Q);
c = A.getLine(h);
}
else
c = A.getLine(N);
if (c) {
for (const h of c)
if (h.hyperlink_id === this.hoveredHyperlinkId || h.hyperlink_id === this.previousHoveredHyperlinkId) {
J.add(N);
break;
}
}
}
this.previousHoveredHyperlinkId = this.hoveredHyperlinkId;
}
if (F) {
if (this.previousHoveredLinkRange)
for (let N = this.previousHoveredLinkRange.startY; N <= this.previousHoveredLinkRange.endY; N++)
J.add(N);
if (this.hoveredLinkRange)
for (let N = this.hoveredLinkRange.startY; N <= this.hoveredLinkRange.endY; N++)
J.add(N);
this.previousHoveredLinkRange = this.hoveredLinkRange;
}
let a = !1;
for (let N = 0; N < w.rows; N++) {
if (!(Q > 0 ? !0 : B || A.isRowDirty(N) || G.has(N) || J.has(N)))
continue;
a = !0;
let h = null;
if (Q > 0)
if (N < Q && g) {
const y = o - Math.floor(Q) + N;
h = g.getScrollbackLine(y);
} else {
const y = Q > 0 ? N - Math.floor(Q) : N;
h = A.getLine(y);
}
else
h = A.getLine(N);
h && this.renderLine(h, N, w.cols);
}
k && a && this.renderSelection(w.cols), Q === 0 && I.visible && this.cursorVisible && this.renderCursor(I.x, I.y), g && C > 0 && this.renderScrollbar(Q, o, w.rows, C), this.lastCursorPosition = { x: I.x, y: I.y }, B || A.clearDirty();
}
/**
* Render a single line
*/
renderLine(A, B, Q) {
const g = B * this.metrics.height;
this.ctx.fillStyle = this.theme.background, this.ctx.fillRect(0, g, Q * this.metrics.width, this.metrics.height);
for (let C = 0; C < A.length; C++) {
const I = A[C];
I.width !== 0 && this.renderCell(I, C, B);
}
}
/**
* Render a single cell
*/
renderCell(A, B, Q) {
const g = B * this.metrics.width, C = Q * this.metrics.height, I = this.metrics.width * A.width;
let w = A.fg_r, o = A.fg_g, i = A.fg_b, M = A.bg_r, k = A.bg_g, G = A.bg_b;
if (A.flags & U.INVERSE && ([w, o, i, M, k, G] = [M, k, G, w, o, i]), this.ctx.fillStyle = this.rgbToCSS(M, k, G), this.ctx.fillRect(g, C, I, this.metrics.height), A.flags & U.INVISIBLE)
return;
let J = "";
A.flags & U.ITALIC && (J += "italic "), A.flags & U.BOLD && (J += "bold "), this.ctx.font = `${J}${this.fontSize}px ${this.fontFamily}`, this.ctx.fillStyle = this.rgbToCSS(w, o, i), A.flags & U.FAINT && (this.ctx.globalAlpha = 0.5);
const s = g, F = C + this.metrics.baseline, a = String.fromCodePoint(A.codepoint || 32);
if (this.ctx.fillText(a, s, F), A.flags & U.FAINT && (this.ctx.globalAlpha = 1), A.flags & U.UNDERLINE) {
const N = C + this.metrics.baseline + 2;
this.ctx.strokeStyle = this.ctx.fillStyle, this.ctx.lineWidth = 1, this.ctx.beginPath(), this.ctx.moveTo(g, N), this.ctx.lineTo(g + I, N), this.ctx.stroke();
}
if (A.flags & U.STRIKETHROUGH) {
const N = C + this.metrics.height / 2;
this.ctx.strokeStyle = this.ctx.fillStyle, this.ctx.lineWidth = 1, this.ctx.beginPath(), this.ctx.moveTo(g, N), this.ctx.lineTo(g + I, N), this.ctx.stroke();
}
if (A.hyperlink_id > 0 && A.hyperlink_id === this.hoveredHyperlinkId) {
const c = C + this.metrics.baseline + 2;
this.ctx.strokeStyle = "#4A90E2", this.ctx.lineWidth = 1, this.ctx.beginPath(), this.ctx.moveTo(g, c), this.ctx.lineTo(g + I, c), this.ctx.stroke();
}
if (this.hoveredLinkRange) {
const N = this.hoveredLinkRange;
if (Q === N.startY && B >= N.startX && (Q < N.endY || B <= N.endX) || Q > N.startY && Q < N.endY || Q === N.endY && B <= N.endX && (Q > N.startY || B >= N.startX)) {
const h = C + this.metrics.baseline + 2;
this.ctx.strokeStyle = "#4A90E2", this.ctx.lineWidth = 1, this.ctx.beginPath(), this.ctx.moveTo(g, h), this.ctx.lineTo(g + I, h), this.ctx.stroke();
}
}
}
/**
* Render cursor
*/
renderCursor(A, B) {
const Q = A * this.metrics.width, g = B * this.metrics.height;
switch (this.ctx.fillStyle = this.theme.cursor, this.cursorStyle) {
case "block":
this.ctx.fillRect(Q, g, this.metrics.width, this.metrics.height);
break;
case "underline":
const C = Math.max(2, Math.floor(this.metrics.height * 0.15));
this.ctx.fillRect(
Q,
g + this.metrics.height - C,
this.metrics.width,
C
);
break;
case "bar":
const I = Math.max(2, Math.floor(this.metrics.width * 0.15));
this.ctx.fillRect(Q, g, I, this.metrics.height);
break;
}
}
// ==========================================================================
// Cursor Blinking
// ==========================================================================
startCursorBlink() {
this.cursorBlinkInterval = window.setInterval(() => {
this.cursorVisible = !this.cursorVisible;
}, 530);
}
stopCursorBlink() {
this.cursorBlinkInterval !== void 0 && (clearInterval(this.cursorBlinkInterval), this.cursorBlinkInterval = void 0), this.cursorVisible = !0;
}
// ==========================================================================
// Public API
// ==========================================================================
/**
* Update theme colors
*/
setTheme(A) {
this.theme = { ...r, ...A }, this.palette = [
this.theme.black,
this.theme.red,
this.theme.green,
this.theme.yellow,
this.theme.blue,
this.theme.magenta,
this.theme.cyan,
this.theme.white,
this.theme.brightBlack,
this.theme.brightRed,
this.theme.brightGreen,
this.theme.brightYellow,
this.theme.brightBlue,
this.theme.brightMagenta,
this.theme.brightCyan,
this.theme.brightWhite
];
}
/**
* Update font size
*/
setFontSize(A) {
this.fontSize = A, this.metrics = this.measureFont();
}
/**
* Update font family
*/
setFontFamily(A) {
this.fontFamily = A, this.metrics = this.measureFont();
}
/**
* Update cursor style
*/
setCursorStyle(A) {
this.cursorStyle = A;
}
/**
* Enable/disable cursor blinking
*/
setCursorBlink(A) {
A && !this.cursorBlink ? (this.cursorBlink = !0, this.startCursorBlink()) : !A && this.cursorBlink && (this.cursorBlink = !1, this.stopCursorBlink());
}
/**
* Get current font metrics
*/
/**
* Render scrollbar (Phase 2)
* Shows scroll position and allows click/drag interaction
* @param opacity Opacity level (0-1) for fade in/out effect
*/
renderScrollbar(A, B, Q, g = 1) {
if (g <= 0 || B === 0)
return;
const C = this.ctx, I = this.canvas.height / this.devicePixelRatio, w = this.canvas.width / this.devicePixelRatio, o = 8, i = w - o - 4, M = 4, k = I - M * 2, G = B + Q, J = Math.max(20, Q / G * k), s = A / B, F = M + (k - J) * (1 - s);
C.fillStyle = `rgba(128, 128, 128, ${0.1 * g})`, C.fillRect(i, M, o, k);
const N = A > 0 ? 0.5 : 0.3;
C.fillStyle = `rgba(128, 128, 128, ${N * g})`, C.fillRect(i, F, o, J);
}
getMetrics() {
return { ...this.metrics };
}
/**
* Get canvas element (needed by SelectionManager)
*/
getCanvas() {
return this.canvas;
}
/**
* Set selection manager (for rendering selection overlay)
*/
setSelectionManager(A) {
this.selectionManager = A;
}
/**
* Set the currently hovered hyperlink ID for rendering underlines
*/
setHoveredHyperlinkId(A) {
this.hoveredHyperlinkId = A;
}
/**
* Set the currently hovered link range for rendering underlines (for regex-detected URLs)
* Pass null to clear the hover state
*/
setHoveredLinkRange(A) {
this.hoveredLinkRange = A;
}
/**
* Get character cell width (for coordinate conversion)
*/
get charWidth() {
return this.metrics.width;
}
/**
* Get character cell height (for coordinate conversion)
*/
get charHeight() {
return this.metrics.height;
}
/**
* Clear entire canvas
*/
clear() {
this.ctx.fillStyle = this.theme.background, this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
}
/**
* Render selection overlay
*/
renderSelection(A) {
const B = this.selectionManager.getSelectionCoords();
if (!B)
return;
const { startCol: Q, startRow: g, endCol: C, endRow: I } = B;
this.ctx.save(), this.ctx.fillStyle = this.theme.selectionBackground, this.ctx.globalAlpha = 0.5;
for (let w = g; w <= I; w++) {
const o = w === g ? Q : 0, i = w === I ? C : A - 1, M = o * this.metrics.width, k = w * this.metrics.height, G = (i - o + 1) * this.metrics.width, J = this.metrics.height;
this.ctx.fillRect(M, k, G, J);
}
this.ctx.restore();
}
/**
* Cleanup resources
*/
dispose() {
this.stopCursorBlink();
}
}
const L = class R {
// ms between scroll steps
constructor(A, B, Q, g) {
this.selectionStart = null, this.selectionEnd = null, this.isSelecting = !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 = A, this.renderer = B, this.wasmTerm = Q, this.textarea = g, this.attachEventListeners();
}
// pixels from edge to trigger scroll
/**
* Get current viewport Y position (how many lines scrolled into history)
*/
getViewportY() {
const A = typeof this.terminal.getViewportY == "function" ? this.terminal.getViewportY() : this.terminal.viewportY || 0;
return Math.max(0, Math.floor(A));
}
/**
* Convert viewport row to absolute buffer row
* Absolute row is an index into combined buffer: scrollback (0 to len-1) + screen (len to len+rows-1)
*/
viewportRowToAbsolute(A) {
const B = this.wasmTerm.getScrollbackLength(), Q = this.getViewportY();
return B + A - Q;
}
/**
* Convert absolute buffer row to viewport row (may be outside visible range)
*/
absoluteRowToViewport(A) {
const B = this.wasmTerm.getScrollbackLength(), Q = this.getViewportY();
return A - B + Q;
}
// ==========================================================================
// Public API
// ==========================================================================
/**
* Get the selected text as a string
*/
getSelection() {
if (!this.selectionStart || !this.selectionEnd)
return "";
let { col: A, absoluteRow: B } = this.selectionStart, { col: Q, absoluteRow: g } = this.selectionEnd;
(B > g || B === g && A > Q) && ([A, Q] = [Q, A], [B, g] = [g, B]);
const C = this.wasmTerm.getScrollbackLength();
let I = "";
for (let w = B; w <= g; w++) {
let o = null;
if (w < C)
o = this.wasmTerm.getScrollbackLine(w);
else {
const J = w - C;
o = this.wasmTerm.getLine(J);
}
if (!o)
continue;
let i = -1;
const M = w === B ? A : 0, k = w === g ? Q : o.length - 1;
let G = "";
for (let J = M; J <= k; J++) {
const s = o[J];
if (s && s.codepoint !== 0) {
const F = String.fromCodePoint(s.codepoint);
G += F, F.trim() && (i = G.length);
} else
G += " ";
}
i >= 0 ? G = G.substring(0, i) : G = "", I += G, w < g && (I += `
`);
}
return I;
}
/**
* Check if there's an active selection
*/
hasSelection() {
return !this.selectionStart || !this.selectionEnd ? !1 : !(this.selectionStart.col === this.selectionEnd.col && this.selectionStart.absoluteRow === this.selectionEnd.absoluteRow);
}
/**
* Clear the selection
*/
clearSelection() {
if (!this.hasSelection())
return;
const A = this.normalizeSelection();
if (A)
for (let B = A.startRow; B <= A.endRow; B++)
this.dirtySelectionRows.add(B);
this.selectionStart = null, this.selectionEnd = null, this.isSelecting = !1, this.requestRender();
}
/**
* Select all text in the terminal
*/
selectAll() {
const A = this.wasmTerm.getDimensions(), B = this.getViewportY();
this.selectionStart = { col: 0, absoluteRow: B }, this.selectionEnd = { col: A.cols - 1, absoluteRow: B + A.rows - 1 }, this.requestRender(), this.selectionChangedEmitter.fire();
}
/**
* Select text at specific column and row with length
* xterm.js compatible API
*/
select(A, B, Q) {
const g = this.wasmTerm.getDimensions();
B = Math.max(0, Math.min(B, g.rows - 1)), A = Math.max(0, Math.min(A, g.cols - 1));
let C = B, I = A + Q - 1;
for (; I >= g.cols; )
I -= g.cols, C++;
C = Math.min(C, g.rows - 1);
const w = this.getViewportY();
this.selectionStart = { col: A, absoluteRow: w + B }, this.selectionEnd = { col: I, absoluteRow: w + C }, this.requestRender(), this.selectionChangedEmitter.fire();
}
/**
* Select entire lines from start to end
* xterm.js compatible API
*/
selectLines(A, B) {
const Q = this.wasmTerm.getDimensions();
A = Math.max(0, Math.min(A, Q.rows - 1)), B = Math.max(0, Math.min(B, Q.rows - 1)), A > B && ([A, B] = [B, A]);
const g = this.getViewportY();
this.selectionStart = { col: 0, absoluteRow: g + A }, this.selectionEnd = { col: Q.cols - 1, absoluteRow: g + B }, this.requestRender(), this.selectionChangedEmitter.fire();
}
/**
* Get selection position as buffer range
* xterm.js compatible API
*/
getSelectionPosition() {
const A = this.normalizeSelection();
if (A)
return {
start: { x: A.startCol, y: A.startRow },
end: { x: A.endCol, y: A.endRow }
};
}
/**
* Deselect all text
* xterm.js compatible API
*/
deselect() {
this.clearSelection(), this.selectionChangedEmitter.fire();
}
/**
* Focus the terminal (make it receive keyboard input)
*/
focus() {
const A = this.renderer.getCanvas();
A.parentElement && A.parentElement.focus();
}
/**
* Get current selection coordinates (for rendering)
*/
getSelectionCoords() {
return this.normalizeSelection();
}
/**
* Get dirty selection rows that need redraw (for clearing old highlight)
*/
getDirtySelectionRows() {
return this.dirtySelectionRows;
}
/**
* Clear the dirty selection rows tracking (after redraw)
*/
clearDirtySelectionRows() {
this.dirtySelectionRows.clear();
}
/**
* Get selection change event accessor
*/
get onSelectionChange() {
return this.selectionChangedEmitter.event;
}
/**
* Cleanup resources
*/
dispose() {
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);
}
// ==========================================================================
// Private Methods
// ==========================================================================
/**
* Attach mouse event listeners to canvas
*/
attachEventListeners() {
const A = this.renderer.getCanvas();
A.addEventListener("mousedown", (B) => {
if (B.button === 0) {
A.parentElement && A.parentElement.focus();
const Q = this.pixelToCell(B.offsetX, B.offsetY);
this.hasSelection() && this.clearSelection();
const C = this.viewportRowToAbsolute(Q.row);
this.selectionStart = { col: Q.col, absoluteRow: C }, this.selectionEnd = { col: Q.col, absoluteRow: C }, this.isSelecting = !0;
}
}), A.addEventListener("mousemove", (B) => {
if (this.isSelecting) {
this.markCurrentSelectionDirty();
const Q = this.pixelToCell(B.offsetX, B.offsetY), g = this.viewportRowToAbsolute(Q.row);
this.selectionEnd = { col: Q.col, absoluteRow: g }, this.requestRender(), this.updateAutoScroll(B.offsetY, A.clientHeight);
}
}), A.addEventListener("mouseleave", (B) => {
if (this.isSelecting) {
const Q = A.getBoundingClientRect();
B.clientY < Q.top ? this.startAutoScroll(-1) : B.clientY > Q.bottom && this.startAutoScroll(1);
}
}), A.addEventListener("mouseenter", () => {
this.isSelecting && this.stopAutoScroll();
}), this.boundDocumentMouseMoveHandler = (B) => {
if (this.isSelecting) {
const Q = A.getBoundingClientRect(), g = Math.max(Q.left, Math.min(B.clientX, Q.right)), C = Math.max(Q.top, Math.min(B.clientY, Q.bottom)), I = g - Q.left, w = C - Q.top;
if ((B.clientX < Q.left || B.clientX > Q.right || B.clientY < Q.top || B.clientY > Q.bottom) && (B.clientY < Q.top ? this.startAutoScroll(-1) : B.clientY > Q.bottom ? this.startAutoScroll(1) : this.stopAutoScroll(), this.autoScrollDirection === 0)) {
this.markCurrentSelectionDirty();
const o = this.pixelToCell(I, w), i = this.viewportRowToAbsolute(o.row);
this.selectionEnd = { col: o.col, absoluteRow: i }, this.requestRender();
}
}
}, document.addEventListener("mousemove", this.boundDocumentMouseMoveHandler), document.addEventListener("mousedown", (B) => {
this.mouseDownTarget = B.target;
}), this.boundMouseUpHandler = (B) => {
if (this.isSelecting) {
this.isSelecting = !1, this.stopAutoScroll();
const Q = this.getSelection();
Q && (this.copyToClipboard(Q), this.selectionChangedEmitter.fire());
}
}, document.addEventListener("mouseup", this.boundMouseUpHandler), A.addEventListener("dblclick", (B) => {
const Q = this.pixelToCell(B.offsetX, B.offsetY), g = this.getWordAtCell(Q.col, Q.row);
if (g) {
const C = this.viewportRowToAbsolute(Q.row);
this.selectionStart = { col: g.startCol, absoluteRow: C }, this.selectionEnd = { col: g.endCol, absoluteRow: C }, this.requestRender();
const I = this.getSelection();
I && (this.copyToClipboard(I), this.selectionChangedEmitter.fire());
}
}), this.boundContextMenuHandler = (B) => {
if (this.renderer.getCanvas().getBoundingClientRect(), this.textarea.style.position = "fixed", this.textarea.style.left = `${B.clientX}px`, this.textarea.style.top = `${B.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()) {
const g = this.getSelection();
this.textarea.value = g, this.textarea.select(), this.textarea.setSelectionRange(0, g.length);
} else
this.textarea.value = "";
this.textarea.focus(), setTimeout(() => {
const g = () => {
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", g), document.removeEventListener("contextmenu", g), this.textarea.removeEventListener("blur", g);
};
document.addEventListener("click", g, { once: !0 }), document.addEventListener("contextmenu", g, { once: !0 }), this.textarea.addEventListener("blur", g, { once: !0 });
}, 10);
}, A.addEventListener("contextmenu", this.boundContextMenuHandler), this.boundClickHandler = (B) => {
if (this.isSelecting || this.mouseDownTarget && A.contains(this.mouseDownTarget))
return;
const g = B.target;
A.contains(g) || this.hasSelection() && this.clearSelection();
}, document.addEventListener("click", this.boundClickHandler);
}
/**
* Mark current selection rows as dirty for redraw
*/
markCurrentSelectionDirty() {
const A = this.normalizeSelection();
if (A)
for (let B = A.startRow; B <= A.endRow; B++)
this.dirtySelectionRows.add(B);
}
/**
* Update auto-scroll based on mouse Y position within canvas
*/
updateAutoScroll(A, B) {
const Q = R.AUTO_SCROLL_EDGE_SIZE;
A < Q ? this.startAutoScroll(-1) : A > B - Q ? this.startAutoScroll(1) : this.stopAutoScroll();
}
/**
* Start auto-scrolling in the given direction
*/
startAutoScroll(A) {
this.autoScrollInterval !== null && this.autoScrollDirection === A || (this.stopAutoScroll(), this.autoScrollDirection = A, this.autoScrollInterval = setInterval(() => {
if (!this.isSelecting) {
this.stopAutoScroll();
return;
}
const B = R.AUTO_SCROLL_SPEED * this.autoScrollDirection;
if (this.terminal.scrollLines(B), this.selectionEnd) {
const Q = this.wasmTerm.getDimensions(), g = this.getViewportY();
if (this.autoScrollDirection < 0) {
const C = g;
C < this.selectionEnd.absoluteRow && (this.selectionEnd = { col: 0, absoluteRow: C });
} else {
const C = g + Q.rows - 1;
C > this.selectionEnd.absoluteRow && (this.selectionEnd = { col: Q.cols - 1, absoluteRow: C });
}
}
this.requestRender();
}, R.AUTO_SCROLL_INTERVAL));
}
/**
* Stop auto-scrolling
*/
stopAutoScroll() {
this.autoScrollInterval !== null && (clearInterval(this.autoScrollInterval), this.autoScrollInterval = null), this.autoScrollDirection = 0;
}
/**
* Convert pixel coordinates to terminal cell coordinates
*/
pixelToCell(A, B) {
const Q = this.renderer.getMetrics(), g = Math.floor(A / Q.width), C = Math.floor(B / Q.height);
return {
col: Math.max(0, Math.min(g, this.terminal.cols - 1)),
row: Math.max(0, Math.min(C, this.terminal.rows - 1))
};
}
/**
* Normalize selection coordinates (handle backward selection)
* Returns coordinates in VIEWPORT space for rendering, clamped to visible area
*/
normalizeSelection() {
if (!this.selectionStart || !this.selectionEnd)
return null;
let { col: A, absoluteRow: B } = this.selectionStart, { col: Q, absoluteRow: g } = this.selectionEnd;
(B > g || B === g && A > Q) && ([A, Q] = [Q, A], [B, g] = [g, B]);
let C = this.absoluteRowToViewport(B), I = this.absoluteRowToViewport(g);
const w = this.wasmTerm.getDimensions(), o = w.rows - 1;
return I < 0 || C > o ? null : (C < 0 && (C = 0, A = 0), I > o && (I = o, Q = w.cols - 1), { startCol: A, startRow: C, endCol: Q, endRow: I });
}
/**
* Get word boundaries at a cell position
*/
getWordAtCell(A, B) {
const Q = this.wasmTerm.getLine(B);
if (!Q)
return null;
const g = (w) => {
if (!w || w.codepoint === 0)
return !1;
const o = String.fromCodePoint(w.codepoint);
return /[\w-]/.test(o);
};
if (!g(Q[A]))
return null;
let C = A;
for (; C > 0 && g(Q[C - 1]); )
C--;
let I = A;
for (; I < Q.length - 1 && g(Q[I + 1]); )
I++;
return { startCol: C, endCol: I };
}
/**
* Copy text to clipboard
*/
async copyToClipboard(A) {
if (navigator.clipboard && navigator.clipboard.writeText)
try {
await navigator.clipboard.writeText(A);
return;
} catch {
}
const B = document.activeElement;
try {
const Q = this.textarea;
Q.value = A, Q.style.position = "fixed", Q.style.left = "-9999px", Q.style.top = "0", Q.style.width = "1px", Q.style.height = "1px", Q.style.opacity = "0", Q.focus(), Q.select(), Q.setSelectionRange(0, A.length);
const g = document.execCommand("copy");
B && B.focus(), g || console.error("❌ execCommand copy failed");
} catch (Q) {
console.error("❌ Fallback copy failed:", Q), B && B.focus();
}
}
/**
* Request a render update (triggers selection overlay redraw)
*/
requestRender() {
}
};
L.AUTO_SCROLL_EDGE_SIZE = 30;
L.AUTO_SCROLL_SPEED = 3;
L.AUTO_SCROLL_INTERVAL = 50;
let $ = L;
class CA {
// 200ms fade animation
constructor(A = {}) {
this.unicode = {
get activeVersion() {
return "15.1";
}
}, this.dataEmitter = new K(), this.resizeEmitter = new K(), this.bellEmitter = new K(), this.selectionChangeEmitter = new K(), this.keyEmitter = new K(), this.titleChangeEmitter = new K(), this.scrollEmitter = new K(), this.renderEmitter = new K(), this.cursorMoveEmitter = new K(), this.onData = this.dataEmitter.event, this.onResize = this.resizeEmitter.event, this.onBell = this.bellEmitter.event, this.onSelectionChange = this.selectionChangeEmitter.event, this.onKey = this.keyEmitter.event, this.onTitleChange = this.titleChangeEmitter.event, this.onScroll = this.scrollEmitter.event, this.onRender = this.renderEmitter.event, this.onCursorMove = this.cursorMoveEmitter.event, this.isOpen = !1, this.isDisposed = !1, this.addons = [], this.currentTitle = "", this.viewportY = 0, this.targetViewportY = 0, this.lastCursorY = 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.animateScroll = () => {
if (!this.wasmTerm || this.scrollAnimationStartTime === void 0)
return;
const Q = this.options.smoothScrollDuration ?? 100, g = this.targetViewportY - this.viewportY;
if (Math.abs(g) < 0.01) {
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;
return;
}
const w = 1 - (1 / (Q / 1e3 * 60)) ** 2;
this.viewportY += g * w;
const o = Math.floor(this.viewportY);
this.scrollEmitter.fire(o), this.getScrollbackLength() > 0 && this.showScrollbar(), this.scrollAnimationFrame = requestAnimationFrame(this.animateScroll);
}, this.handleMouseMove = (Q) => {
if (!(!this.canvas || !this.renderer || !this.wasmTerm)) {
if (this.isDraggingScrollbar) {
this.processScrollbarDrag(Q);
return;
}
if (this.linkDetector) {
if (this.mouseMoveThrottleTimeout) {
this.pendingMouseMove = Q;
return;
}
this.processMouseMove(Q), this.mouseMoveThrottleTimeout = window.setTimeout(() => {
if (this.mouseMoveThrottleTimeout = void 0, this.pendingMouseMove) {
const g = this.pendingMouseMove;
this.pendingMouseMove = void 0, this.processMouseMove(g);
}
}, 16);
}
}
}, this.handleMouseLeave = () => {
var Q, g;
this.renderer && this.wasmTerm && ((this.renderer.hoveredHyperlinkId || 0) > 0 && this.renderer.setHoveredHyperlinkId(0), this.renderer.setHoveredLinkRange(null)), this.currentHoveredLink && ((g = (Q = this.currentHoveredLink).hover) == null || g.call(Q, !1), this.currentHoveredLink = void 0, this.element && (this.element.style.cursor = "text"));
}, this.handleClick = async (Q) => {
if (!this.canvas || !this.renderer || !this.linkDetector || !this.wasmTerm)
return;
const g = this.canvas.getBoundingClientRect(), C = Math.floor((Q.clientX - g.left) / this.renderer.charWidth), w = Math.floor((Q.clientY - g.top) / this.renderer.charHeight), o = this.wasmTerm.getScrollbackLength();
let i;
const M = this.getViewportY(), k = Math.max(0, Math.floor(M));
if (k > 0)
if (w < k)
i = o - k + w;
else {
const J = w - k;
i = o + J;
}
else
i = o + w;
const G = await this.linkDetector.getLinkAt(C, i);
G && (G.activate(Q), (Q.ctrlKey || Q.metaKey) && Q.preventDefault());
}, this.handleWheel = (Q) => {
var C, I, w;
if (Q.preventDefault(), Q.stopPropagation(), this.customWheelEventHandler && this.customWheelEventHandler(Q))
return;
if (((C = this.wasmTerm) == null ? void 0 : C.isAlternateScreen()) ?? !1) {
const o = Q.deltaY > 0 ? "down" : "up", i = Math.min(Math.abs(Math.round(Q.deltaY / 33)), 5);
for (let M = 0; M < i; M++)
o === "up" ? this.dataEmitter.fire("\x1B[A") : this.dataEmitter.fire("\x1B[B");
} else {
let o;
if (Q.deltaMode === WheelEvent.DOM_DELTA_PIXEL) {
const i = ((w = (I = this.renderer) == null ? void 0 : I.getMetrics()) == null ? void 0 : w.height) ?? 20;
o = Q.deltaY / i;
} else
Q.deltaMode === WheelEvent.DOM_DELTA_LINE ? o = Q.deltaY : Q.deltaMode === WheelEvent.DOM_DELTA_PAGE ? o = Q.deltaY * this.rows : o = Q.deltaY / 33;
if (o !== 0) {
const i = this.viewportY - o;
this.smoothScrollTo(i);
}
}
}, this.handleMouseDown = (Q) => {
if (!this.canvas || !this.renderer || !this.wasmTerm)
return;
const g = this.wasmTerm.getScrollbackLength();
if (g === 0)
return;
const C = this.canvas.getBoundingClientRect(), I = Q.clientX - C.left, w = Q.clientY - C.top, o = C.width, i = C.height, M = 8, k = o - M - 4, G = 4;
if (I >= k && I <= k + M) {
Q.preventDefault(), Q.stopPropagation(), Q.stopImmediatePropagation();
const J = i - G * 2, s = this.rows, F = g + s, a = Math.max(20, s / F * J), N = this.viewportY / g, c = G + (J - a) * (1 - N);
if (w >= c && w <= c + a)
this.isDraggingScrollbar = !0, this.scrollbarDragStart = w, this.scrollbarDragStartViewportY = this.viewportY, this.canvas && (this.canvas.style.userSelect = "none", this.canvas.style.webkitUserSelect = "none");
else {
const y = 1 - (w - G) / J, O = Math.round(y * g);
this.scrollToLine(Math.max(0, Math.min(g, O)));
}
}
}, this.handleMouseUp = () => {
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());
}, this.ghostty = A.ghostty ?? gA();
const B = {
cols: A.cols ?? 80,
rows: A.rows ?? 24,
cursorBlink: A.cursorBlink ?? !1,
cursorStyle: A.cursorStyle ?? "block",
theme: A.theme ?? {},
scrollback: A.scrollback ?? 1e3,
fontSize: A.fontSize ?? 15,
fontFamily: A.fontFamily ?? "monospace",
allowTransparency: A.allowTransparency ?? !1,
convertEol: A.convertEol ?? !1,
disableStdin: A.disableStdin ?? !1,
smoothScrollDuration: A.smoothScrollDuration ?? 100
// Default: 100ms smooth scroll
};
this.options = new Proxy(B, {
set: (Q, g, C) => {
const I = Q[g];
return Q[g] = C, this.isOpen && this.handleOptionChange(g, C, I), !0;
}
}), this.cols = this.options.cols, this.rows = this.options.rows, this.buffer = new V(this);
}
// ==========================================================================
// Theme to WASM Config Conversion
// ==========================================================================
/**
* Parse a CSS color string to 0xRRGGBB format.
* Returns 0 if the color is undefined or invalid.
*/
parseColorToHex(A) {
if (!A)
return 0;
if (A.startsWith("#")) {
let Q = A.slice(1);
Q.length === 3 && (Q = Q[0] + Q[0] + Q[1] + Q[1] + Q[2] + Q[2]);
const g = Number.parseInt(Q, 16);
return Number.isNaN(g) ? 0 : g;
}
const B = A.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
if (B) {
const Q = Number.parseInt(B[1], 10), g = Number.parseInt(B[2], 10), C = Number.parseInt(B[3], 10);
return Q << 16 | g << 8 | C;
}
return 0;
}
/**
* Convert terminal options to WASM terminal config.
*/
buildWasmConfig() {
const A = this.options.theme, B = this.options.scrollback;
if (!A && B === 1e3)
return;
const Q = [
this.parseColorToHex(A == null ? void 0 : A.black),
this.parseColorToHex(A == null ? void 0 : A.red),
this.parseColorToHex(A == null ? void 0 : A.green),
this.parseColorToHex(A == null ? void 0 : A.yellow),
this.parseColorToHex(A == null ? void 0 : A.blue),
this.parseColorToHex(A == null ? void 0 : A.magenta),
this.parseColorToHex(A == null ? void 0 : A.cyan),
this.parseColorToHex(A == null ? void 0 : A.white),
this.parseColorToHex(A == null ? void 0 : A.brightBlack),
this.parseColorToHex(A == null ? void 0 : A.brightRed),
this.parseColorToHex(A == null ? void 0 : A.brightGreen),
this.parseColorToHex(A == null ? void 0 : A.brightYellow),
this.parseColorToHex(A == null ? void 0 : A.brightBlue),
this.parseColorToHex(A == null ? void 0 : A.brightMagenta),
this.parseColorToHex(A == null ? void 0 : A.brightCyan),
this.parseColorToHex(A == null ? void 0 : A.brightWhite)
];
return {
scrollbackLimit: B,
fgColor: this.parseColorToHex(A == null ? void 0 : A.foreground),
bgColor: this.parseColorToHex(A == null ? void 0 : A.background),
cursorColor: this.parseColorToHex(A == null ? void 0 : A.cursor),
palette: Q
};
}
// ==========================================================================
// Option Change Handling (for mutable options)
// ==========================================================================
/**
* Handle runtime option changes (called when options are modified after terminal is open)
* This enables xterm.js compatibility where options can be changed at runtime
*/
handleOptionChange(A, B, Q) {
if (B !== Q)
switch (A) {
case "disableStdin":
break;
case "cursorBlink":
case "cursorStyle":
this.renderer && (this.renderer.setCursorStyle(this.options.cursorStyle), this.renderer.setCursorBlink(this.options.cursorBlink));
break;
case "theme":
this.renderer && console.warn("ghostty-web: theme changes after open() are not yet fully supported");
break;
case "fontSize":
case "fontFamily":
this.renderer && console.warn("ghostty-web: font changes after open() are not yet fully supported");
break;
case "cols":
case "rows":
this.resize(this.options.cols, this.options.rows);
break;
}
}
// ==========================================================================
// Lifecycle Methods
// ==========================================================================
/**
* Open terminal in a parent element
*
* Initializes all components and starts rendering.
* Requires a pre-loaded Ghostty instance passed to the constructor.
*/
open(A) {
if (this.isOpen)
throw new Error("Terminal is already open");
if (this.isDisposed)
throw new Error("Terminal has been disposed");
this.element = A, this.isOpen = !0;
try {
A.hasAttribute("tabindex") || A.setAttribute("tabindex", "0");
const B = this.buildWasmConfig();
this.wasmTerm = this.ghostty.createTerminal(this.cols, this.rows, B), this.canvas = document.createElement("canvas"), this.canvas.style.display = "block", A.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", "-1"), 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 = "0", this.textarea.style.height = "0", this.textarea.style.zIndex = "-10", this.textarea.style.opacity = "0", this.textarea.style.overflow = "hidden", this.textarea.style.pointerEvents = "none", this.textarea.style.resize = "none", this.textarea.style.border = "none", this.textarea.style.outline = "none", A.appendChild(this.textarea), this.renderer = new _(this.canvas, {
fontSize: this.options.fontSize,
fontFamily: this.options.fontFamily,
cursorStyle: this.options.cursorStyle,
cursorBlink: this.options.cursorBlink,
theme: this.options.theme
}), this.renderer.resize(this.cols, this.rows), this.inputHandler = new X(
this.ghostty,
A,
(Q) => {
this.options.disableStdin || this.dataEmitter.fire(Q);
},
() => {
this.bellEmitter.fire();
},
(Q) => {
this.keyEmitter.fire(Q);
},
this.customKeyEventHandler
), this.selectionManager = new $(
this,
this.renderer,
this.wasmTerm,
this.textarea
), this.renderer.setSelectionManager(this.selectionManager), this.selectionManager.onSelectionChange(() => {
this.selectionChangeEmitter.fire();
}), this.textarea.addEventListener("paste", (Q) => {
var C;
Q.preventDefault(), Q.stopPropagation();
const g = (C = Q.clipboardData) == null ? void 0 : C.getData("text");
g && this.paste(g);
}), this.linkDetector = new P(this), this.linkDetector.registerProvider(new v(this)), this.linkDetector.registerProvider(new u(this)), A.addEventListener("mousedown", this.handleMouseDown, { capture: !0 }), A.addEventListener("mousemove", this.handleMouseMove), A.addEventListener("mouseleave", this.handleMouseLeave), A.addEventListener("click", this.handleClick), document.addEventListener("mouseup", this.handleMouseUp), A.addEventListener("wheel", this.handleWheel, { passive: !1, capture: !0 }), this.renderer.render(this.wasmTerm, !0, this.viewportY, this, this.scrollbarOpacity), this.startRenderLoop(), this.focus();
} catch (B) {
throw this.isOpen = !1, this.cleanupComponents(), new Error(`Failed to open terminal: ${B}`);
}
}
/**
* Write data to terminal
*/
write(A, B) {
this.assertOpen(), this.options.convertEol && typeof A == "string" && (A = A.replace(/\n/g, `\r
`)), this.writeInternal(A, B);
}
/**
* Internal write implementation (extracted from write())
*/
writeInternal(A, B) {
var Q;
this.wasmTerm.write(A), typeof A == "string" && A.includes("\x07") ? this.bellEmitter.fire() : A instanceof Uint8Array && A.includes(7) && this.bellEmitter.fire(), (Q = this.linkDetector) == null || Q.invalidateCache(), this.viewportY !== 0 && this.scrollToBottom(), typeof A == "string" && A.includes("\x1B]") && this.checkForTitleChange(A), B && requestAnimationFrame(B);
}
/**
* Write data with newline
*/
writeln(A, B) {
if (typeof A == "string")
this.write(A + `\r
`, B);
else {
const Q = new Uint8Array(A.length + 2);
Q.set(A), Q[A.length] = 13, Q[A.length + 1] = 10, this.write(Q, B);
}
}
/**
* Paste text into terminal (triggers bracketed paste if supported)
*/
paste(A) {
this.assertOpen(), !this.options.disableStdin && (this.wasmTerm.hasBracketedPaste() ? this.dataEmitter.fire("\x1B[200~" + A + "\x1B[201~") : this.dataEmitter.fire(A));
}
/**
* Input data into terminal (as if typed by user)
*
* @param data - Data to input
* @param wasUserInput - If true, triggers onData event (default: false for compat with some apps)
*/
input(A, B = !1) {
this.assertOpen(), !this.options.disableStdin && (B ? this.dataEmitter.fire(A) : this.write(A));
}
/**
* Resize terminal
*/
resize(A, B) {
if (this.assertOpen(), A === this.cols && B === this.rows)
return;
this.cols = A, this.rows = B, this.wasmTerm.resize(A, B), this.renderer.resize(A, B);
const Q = this.renderer.getMetrics();
this.canvas.width = Q.width * A, this.canvas.height = Q.height * B, this.canvas.style.width = `${Q.width * A}px`, this.canvas.style.height = `${Q.height * B}px`, this.resizeEmitter.fire({ cols: A, rows: B }), this.renderer.render(this.wasmTerm, !0, this.viewportY, this);
}
/**
* Clear terminal screen
*/
clear() {
this.assertOpen(), this.wasmTerm.write("\x1B[2J\x1B[H");
}
/**
* Reset terminal state
*/
reset() {
this.assertOpen(), this.wasmTerm && this.wasmTerm.free();
const A = this.buildWasmConfig();
this.wasmTerm = this.ghostty.createTerminal(this.cols, this.rows, A), this.renderer.clear(), this.currentTitle = "";
}
/**
* Focus terminal input
*/
focus() {
this.isOpen && this.element && (this.element.focus(), setTimeout(() => {
var A;
(A = this.element) == null || A.focus();
}, 0));
}
/**
* Blur terminal (remove focus)
*/
blur() {
this.isOpen && this.element && this.element.blur();
}
/**
* Load an addon
*/
loadAddon(A) {
A.activate(this), this.addons.push(A);
}
// ==========================================================================
// Selection API (xterm.js compatible)
// ==========================================================================
/**
* Get the selected text as a string
*/
getSelection() {
var A;
return ((A = this.selectionManager) == null ? void 0 : A.getSelection()) || "";
}
/**
* Check if there's an active selection
*/
hasSelection() {
var A;
return ((A = this.selectionManager) == null ? void 0 : A.hasSelection()) || !1;
}
/**
* Clear the current selection
*/
clearSelection() {
var A;
(A = this.selectionManager) == null || A.clearSelection();
}
/**
* Select all text in the terminal
*/
selectAll() {
var A;
(A = this.selectionManager) == null || A.selectAll();
}
/**
* Select text at specific column and row with length
*/
select(A, B, Q) {
var g;
(g = this.selectionManager) == null || g.select(A, B, Q);
}
/**
* Select entire lines from start to end
*/
selectLines(A, B) {
var Q;
(Q = this.selectionManager) == null || Q.selectLines(A, B);
}
/**
* Get selection position as buffer range
*/
/**
* Get the current viewport Y position.
*
* This is the number of lines scrolled back from the bottom of the
* scrollback buffer. It may be fractional during smooth scrolling.
*/
getViewportY() {
return this.viewportY;
}
getSelectionPosition() {
var A;
return (A = this.selectionManager) == null ? void 0 : A.getSelectionPosition();
}
// ==========================================================================
// Phase 1: Custom Event Handlers
// ==========================================================================
/**
* Attach a custom keyboard event handler
* Returns true to prevent default handling
*/
attachCustomKeyEventHandler(A) {
this.customKeyEventHandler = A, this.inputHandler && this.inputHandler.setCustomKeyEventHandler(A);
}
/**
* Attach a custom wheel event handler (Phase 2)
* Returns true to prevent default handling
*/
attachCustomWheelEventHandler(A) {
this.customWheelEventHandler = A;
}
// ==========================================================================
// Link Detection Methods
// ==========================================================================
/**
* Register a custom link provider
* Multiple providers can be registered to detect different types of links
*
* @example
* ```typescript
* term.registerLinkProvider({
* provideLinks(y, callback) {
* // Detect URLs, file paths, etc.
* callback(detectedLinks);
* }
* });
* ```
*/
registerLinkProvider(A) {
if (!this.linkDetector)
throw new Error("Terminal must be opened before registering link providers");
this.linkDetector.registerProvider(A);
}
// ==========================================================================
// Phase 2: Scrolling Methods
// ==========================================================================
/**
* Scroll viewport by a number of lines
* @param amount Number of lines to scroll (positive = down, negative = up)
*/
scrollLines(A) {
if (!this.wasmTerm)
throw new Error("Terminal not open");
const B = this.getScrollbackLength(), g = Math.max(0, Math.min(B, this.viewportY - A));
g !== this.viewportY && (this.viewportY = g, this.scrollEmitter.fire(this.viewportY), B > 0 && this.showScrollbar());
}
/**
* Scroll viewport by a number of pages
* @param amount Number of pages to scroll (positive = down, negative = up)
*/
scrollPages(A) {
this.scrollLines(A * this.rows);
}
/**
* Scroll viewport to the top of the scrollback buffer
*/
scrollToTop() {
const A = this.getScrollbackLength();
A > 0 && this.viewportY !== A && (this.viewportY = A, this.scrollEmitter.fire(this.viewportY), this.showScrollbar());
}
/**
* Scroll viewport to the bottom (current output)
*/
scrollToBottom() {
this.viewportY !== 0 && (this.viewportY = 0, this.scrollEmitter.fire(this.viewportY), this.getScrollbackLength() > 0 && this.showScrollbar());
}
/**
* Scroll viewport to a specific line in the buffer
* @param line Line number (0 = top of scrollback, scrollbackLength = bottom)
*/
scrollToLine(A) {
const B = this.getScrollbackLength(), Q = Math.max(0, Math.min(B, A));
Q !== this.viewportY && (this.viewportY = Q, this.scrollEmitter.fire(this.viewportY), B > 0 && this.showScrollbar());
}
/**
* Smoothly scroll to a target viewport position
* @param targetY Target viewport Y position (in lines, can be fractional)
*/
smoothScrollTo(A) {
if (!this.wasmTerm)
return;
const B = this.getScrollbackLength(), g = Math.max(0, Math.min(B, A));
if ((this.options.smoothScrollDuration ?? 100) === 0) {
this.viewportY = g, this.targetViewportY = g, this.scrollEmitter.fire(Math.floor(this.viewportY)), B > 0 && this.showScrollbar();
return;
}
this.targetViewportY = g, !this.scrollAnimationFrame && (this.scrollAnimationStartTime = Date.now(), this.scrollAnimationStartY = this.viewportY, this.animateScroll());
}
// ==========================================================================
// Lifecycle
// ==========================================================================
/**
* Dispose terminal and clean up resources
*/
dispose() {
if (!this.isDisposed) {
this.isDisposed = !0, this.isOpen = !1, this.animationFrameId && (cancelAnimationFrame(this.animationFrameId), this.animationFrameId = void 0), this.scrollAnimationFrame && (cancelAnimationFrame(this.scrollAnimationFrame), this.scrollAnimationFrame = void 0), this.mouseMoveThrottleTimeout && (clearTimeout(this.mouseMoveThrottleTimeout), this.mouseMoveThrottleTimeout = void 0), this.pendingMouseMove = void 0;
for (const A of this.addons)
A.dispose();
this.addons = [], this.cleanupComponents(), this.dataEmitter.dispose(), this.resizeEmitter.dispose(), this.bellEmitter.dispose(), this.selectionChangeEmitter.dispose(), this.keyEmitter.dispose(), this.titleChangeEmitter.dispose(), this.scrollEmitter.dispose(), this.renderEmitter.dispose(), this.cursorMoveEmitter.dispose();
}
}
// ==========================================================================
// Private Methods
// ==========================================================================
/**
* Start the render loop
*/
startRenderLoop() {
const A = () => {
if (!this.isDisposed && this.isOpen) {
const B = this.wasmTerm.getCursor();
B.y !== this.lastCursorY && (this.lastCursorY = B.y, this.cursorMoveEmitter.fire()), this.renderer.render(this.wasmTerm, !1, this.viewportY, this, this.scrollbarOpacity), this.animationFrameId = requestAnimationFrame(A);
}
};
A();
}
/**
* Get a line from native WASM scrollback buffer
* Implements IScrollbackProvider
*/
getScrollbackLine(A) {
return this.wasmTerm ? this.wasmTerm.getScrollbackLine(A) : null;
}
/**
* Get scrollback length from native WASM
* Implements IScrollbackProvider
*/
getScrollbackLength() {
return this.wasmTerm ? this.wasmTerm.getScrollbackLength() : 0;
}
/**
* Clean up components (called on dispose or error)
*/
cleanupComponents() {
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.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.wasmTerm && (this.wasmTerm.free(), this.wasmTerm = void 0), this.ghostty = void 0, this.element = void 0, this.textarea = void 0;
}
/**
* Assert terminal is open (throw if not)
*/
assertOpen() {
if (this.isDisposed)
throw new Error("Terminal has been disposed");
if (!this.isOpen)
throw new Error("Terminal must be opened before use. Call terminal.open(parent) first.");
}
/**
* Process mouse move for link detection (internal, called by throttled handler)
*/
processMouseMove(A) {
if (!this.canvas || !this.renderer || !this.linkDetector || !this.wasmTerm)
return;
const B = this.canvas.getBoundingClientRect(), Q = Math.floor((A.clientX - B.left) / this.renderer.charWidth), C = Math.floor((A.clientY - B.top) / this.renderer.charHeight);
let I = 0, w = null;
const o = this.getViewportY(), i = Math.max(0, Math.floor(o));
if (i > 0) {
const F = this.wasmTerm.getScrollbackLength();
if (C < i) {
const a = F - i + C;
w = this.wasmTerm.getScrollbackLine(a);
} else {
const a = C - i;
w = this.wasmTerm.getLine(a);
}
} else
w = this.wasmTerm.getLine(C);
w && Q >= 0 && Q < w.length && (I = w[Q].hyperlink_id);
const M = this.renderer.hoveredHyperlinkId || 0;
I !== M && this.renderer.setHoveredHyperlinkId(I);
const k = this.wasmTerm.getScrollbackLength();
let G;
const J = this.getViewportY(), s = Math.max(0, Math.floor(J));
if (s > 0)
if (C < s)
G = k - s + C;
else {
const F = C - s;
G = k + F;
}
else
G = k + C;
this.linkDetector.getLinkAt(Q, G).then((F) => {
var a, N, c, h;
if (F !== this.currentHoveredLink && ((N = (a = this.currentHoveredLink) == null ? void 0 : a.hover) == null || N.call(a, !1), this.currentHoveredLink = F, (c = F == null ? void 0 : F.hover) == null || c.call(F, !0), this.element && (this.element.style.cursor = F ? "pointer" : "text"), this.renderer))
if (F) {
const y = ((h = this.wasmTerm) == null ? void 0 : h.getScrollbackLength()) || 0, O = this.getViewportY(), p = Math.max(0, Math.floor(O)), T = F.range.start.y - y + p, l = F.range.end.y - y + p;
T < this.rows && l >= 0 ? this.renderer.setHoveredLinkRange({
startX: F.range.start.x,
startY: Math.max(0, T),
endX: F.range.end.x,
endY: Math.min(this.rows - 1, l)
}) : this.renderer.setHoveredLinkRange(null);
} else
this.renderer.setHoveredLinkRange(null);
}).catch((F) => {
console.warn("Link detection error:", F);
});
}
/**
* Process scrollbar drag movement
*/
processScrollbarDrag(A) {
if (!this.canvas || !this.renderer || !this.wasmTerm || this.scrollbarDragStart === null)
return;
const B = this.wasmTerm.getScrollbackLength();
if (B === 0)
return;
const Q = this.canvas.getBoundingClientRect(), C = A.clientY - Q.top - this.scrollbarDragStart, o = Q.height - 4 * 2, i = this.rows, M = B + i, k = Math.max(20, i / M * o), G = -C / (o - k), J = Math.round(G * B), s = this.scrollbarDragStartViewportY + J;
this.scrollToLine(Math.max(0, Math.min(B, s)));
}
/**
* Show scrollbar with fade-in and schedule auto-hide
*/
showScrollbar() {
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(() => {
this.hideScrollbar();
}, this.SCROLLBAR_HIDE_DELAY_MS));
}
/**
* Hide scrollbar with fade-out
*/
hideScrollbar() {
this.scrollbarHideTimeout && (window.clearTimeout(this.scrollbarHideTimeout), this.scrollbarHideTimeout = void 0), this.scrollbarVisible && this.fadeOutScrollbar();
}
/**
* Fade in scrollbar
*/
fadeInScrollbar() {
const A = Date.now(), B = () => {
const Q = Date.now() - A, g = Math.min(Q / this.SCROLLBAR_FADE_DURATION_MS, 1);
this.scrollbarOpacity = g, g < 1 && requestAnimationFrame(B);
};
B();
}
/**
* Fade out scrollbar
*/
fadeOutScrollbar() {
const A = Date.now(), B = this.scrollbarOpacity, Q = () => {
const g = Date.now() - A, C = Math.min(g / this.SCROLLBAR_FADE_DURATION_MS, 1);
this.scrollbarOpacity = B * (1 - C), C < 1 ? requestAnimationFrame(Q) : (this.scrollbarVisible = !1, this.scrollbarOpacity = 0);
};
Q();
}
/**
* Check for title changes in written data (OSC sequences)
* Simplified implementation - looks for OSC 0, 1, 2
*/
checkForTitleChange(A) {
const B = /\x1b\]([012]);([^\x07\x1b]*?)(?:\x07|\x1b\\)/g;
let Q = null;
for (; (Q = B.exec(A)) !== null; ) {
const g = Q[1], C = Q[2];
(g === "0" || g === "2") && C !== this.currentTitle && (this.currentTitle = C, this.titleChangeEmitter.fire(C));
}
}
// ============================================================================
// Terminal Modes
// ============================================================================
/**
* Query terminal mode state
*
* @param mode Mode number (e.g., 2004 for bracketed paste)
* @param isAnsi True for ANSI modes, false for DEC modes (default: false)
* @returns true if mode is enabled
*/
getMode(A, B = !1) {
return this.assertOpen(), this.wasmTerm.getMode(A, B);
}
/**
* Check if bracketed paste mode is enabled
*/
hasBracketedPaste() {
return this.assertOpen(), this.wasmTerm.hasBracketedPaste();
}
/**
* Check if focus event reporting is enabled
*/
hasFocusEvents() {
return this.assertOpen(), this.wasmTerm.hasFocusEvents();
}
/**
* Check if mouse tracking is enabled
*/
hasMouseTracking() {
return this.assertOpen(), this.wasmTerm.hasMouseTracking();
}
}
const AA = 2, QA = 1, BA = 15, EA = 100;
class IA {
constructor() {
this._isResizing = !1;
}
/**
* Activate the addon (called by Terminal.loadAddon)
*/
activate(A) {
this._terminal = A;
}
/**
* Dispose the addon and clean up resources
*/
dispose() {
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;
}
/**
* Fit the terminal to its container
*
* Calculates optimal dimensions and resizes the terminal.
* Does nothing if dimensions cannot be calculated or haven't changed.
*/
fit() {
if (this._isResizing)
return;
const A = this.proposeDimensions();
if (!A || !this._terminal)
return;
const B = this._terminal, Q = B.cols, g = B.rows;
if (!(A.cols === this._lastCols && A.rows === this._lastRows || A.cols === Q && A.rows === g)) {
this._lastCols = A.cols, this._lastRows = A.rows, this._isResizing = !0;
try {
B.resize && typeof B.resize == "function" && B.resize(A.cols, A.rows);
} finally {
setTimeout(() => {
this._isResizing = !1;
}, 50);
}
}
}
/**
* Propose dimensions to fit the terminal to its container
*
* Calculates cols and rows based on:
* - Terminal container element dimensions (clientWidth/Height)
* - Terminal element padding
* - Font metrics (character cell size)
* - Scrollbar width reservation
*
* @returns Proposed dimensions or undefined if cannot calculate
*/
proposeDimensions() {
var a;
if (!((a = this._terminal) != null && a.element))
return;
const B = this._terminal.renderer;
if (!B || typeof B.getMetrics != "function")
return;
const Q = B.getMetrics();
if (!Q || Q.width === 0 || Q.height === 0)
return;
const g = this._terminal.element;
if (typeof g.clientWidth > "u")
return;
const C = window.getComputedStyle(g), I = Number.parseInt(C.getPropertyValue("padding-top")) || 0, w = Number.parseInt(C.getPropertyValue("padding-bottom")) || 0, o = Number.parseInt(C.getPropertyValue("padding-left")) || 0, i = Number.parseInt(C.getPropertyValue("padding-right")) || 0, M = g.clientWidth, k = g.clientHeight;
if (M === 0 || k === 0)
return;
const G = M - o - i - BA, J = k - I - w, s = Math.max(AA, Math.floor(G / Q.width)), F = Math.max(QA, Math.floor(J / Q.height));
return { cols: s, rows: F };
}
/**
* Observe the terminal's container for resize events
*
* Sets up a ResizeObserver to automatically call fit() when the
* container size changes. Resize events are debounced to avoid
* excessive calls during window drag operations.
*
* Call dispose() to stop observing.
*/
observeResize() {
var A;
(A = this._terminal) != null && A.element && (this._resizeObserver || (this._resizeObserver = new ResizeObserver((B) => {
this._isResizing || !B[0] || (this._resizeDebounceTimer && clearTimeout(this._resizeDebounceTimer), this._resizeDebounceTimer = setTimeout(() => {
this.fit();
}, EA));
}), this._resizeObserver.observe(this._terminal.element)));
}
}
let t = null;
async function DA() {
t || (t = await x.load());
}
function gA() {
if (!t)
throw new Error(
`ghostty-web not initialized. Call init() before creating Terminal instances.
Example:
import { init, Terminal } from "ghostty-web";
await init();
const term = new Terminal();
For tests, pass a Ghostty instance directly:
import { Ghostty, Terminal } from "ghostty-web";
const ghostty = await Ghostty.load();
const term = new Terminal({ ghostty });`
);
return t;
}
export {
_ as CanvasRenderer,
U as CellFlags,
K as EventEmitter,
IA as FitAddon,
x as Ghostty,
j as GhosttyTerminal,
X as InputHandler,
z as KeyEncoder,
m as KeyEncoderOption,
P as LinkDetector,
v as OSC8LinkProvider,
$ as SelectionManager,
CA as Terminal,
u as UrlRegexProvider,
gA as getGhostty,
DA as init
};