@aurigax-ai/ghostty-web 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/ghostty.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  * grid references and key encoder through `Abi`.
7
7
  */
8
8
 
9
- import { Abi, GHOSTTY_SUCCESS, type VtExports } from './abi';
9
+ import { Abi, GHOSTTY_SUCCESS, GhosttyCallError, type VtExports } from './abi';
10
10
  import { EventEmitter } from './event-emitter';
11
11
  import type { IEvent } from './interfaces';
12
12
  import {
@@ -232,8 +232,136 @@ function rgbAt(view: DataView, ptr: number): RGB {
232
232
  return { r: view.getUint8(ptr), g: view.getUint8(ptr + 1), b: view.getUint8(ptr + 2) };
233
233
  }
234
234
 
235
- function bitsOf(raw: bigint, field: { lsb: number; width: number }): number {
236
- return Number((raw >> BigInt(field.lsb)) & ((1n << BigInt(field.width)) - 1n));
235
+ interface CellLayout {
236
+ styleSize: number;
237
+ fgColor: number;
238
+ bgColor: number;
239
+ bold: number;
240
+ italic: number;
241
+ faint: number;
242
+ blink: number;
243
+ inverse: number;
244
+ invisible: number;
245
+ strikethrough: number;
246
+ underline: number;
247
+ colorTag: number;
248
+ colorValue: number;
249
+ colorPalette: number;
250
+ colorRgb: number;
251
+ tagCodepoint: number;
252
+ tagGrapheme: number;
253
+ tagBgPalette: number;
254
+ tagBgRgb: number;
255
+ wideWide: number;
256
+ wideSpacerTail: number;
257
+ rowIteratorData: number;
258
+ rowDirtyData: number;
259
+ rowCellsData: number;
260
+ rowIteratorNext: (iterator: number) => number;
261
+ rowGet: (iterator: number, key: number, out: number) => number;
262
+ rowCellsNext: (cells: number) => number;
263
+ rowCellsGetMulti: (
264
+ cells: number,
265
+ count: number,
266
+ keys: number,
267
+ values: number,
268
+ written: number
269
+ ) => number;
270
+ }
271
+
272
+ const EXPECTED_CELL_BITS: Record<string, [number, number]> = {
273
+ content_tag: [0, 2],
274
+ content: [2, 24],
275
+ style_id: [26, 16],
276
+ wide: [42, 2],
277
+ hyperlink: [45, 1],
278
+ };
279
+
280
+ const cellLayouts = new WeakMap<Abi, CellLayout>();
281
+
282
+ function cellLayout(abi: Abi): CellLayout {
283
+ const cached = cellLayouts.get(abi);
284
+ if (cached) return cached;
285
+ for (const [field, [lsb, width]] of Object.entries(EXPECTED_CELL_BITS)) {
286
+ const bits = abi.bits('GhosttyCell', field);
287
+ if (bits.lsb !== lsb || bits.width !== width) {
288
+ throw new Error(`libghostty-vt GhosttyCell.${field} moved to ${bits.lsb}+${bits.width}`);
289
+ }
290
+ }
291
+ const style = (f: string) => abi.offset('GhosttyStyle', f);
292
+ const tags = 'GhosttyCellContentTag';
293
+ const fn = <T>(name: string) => abi.fn(name) as unknown as T;
294
+ const layout: CellLayout = {
295
+ styleSize: abi.sizeOf('GhosttyStyle'),
296
+ fgColor: style('fg_color'),
297
+ bgColor: style('bg_color'),
298
+ bold: style('bold'),
299
+ italic: style('italic'),
300
+ faint: style('faint'),
301
+ blink: style('blink'),
302
+ inverse: style('inverse'),
303
+ invisible: style('invisible'),
304
+ strikethrough: style('strikethrough'),
305
+ underline: style('underline'),
306
+ colorTag: abi.offset('GhosttyStyleColor', 'tag'),
307
+ colorValue: abi.offset('GhosttyStyleColor', 'value'),
308
+ colorPalette: abi.enumValue('GhosttyStyleColorTag', 'PALETTE'),
309
+ colorRgb: abi.enumValue('GhosttyStyleColorTag', 'RGB'),
310
+ tagCodepoint: abi.enumValue(tags, 'CODEPOINT'),
311
+ tagGrapheme: abi.enumValue(tags, 'CODEPOINT_GRAPHEME'),
312
+ tagBgPalette: abi.enumValue(tags, 'BG_COLOR_PALETTE'),
313
+ tagBgRgb: abi.enumValue(tags, 'BG_COLOR_RGB'),
314
+ wideWide: abi.enumValue('GhosttyCellWide', 'WIDE'),
315
+ wideSpacerTail: abi.enumValue('GhosttyCellWide', 'SPACER_TAIL'),
316
+ rowIteratorData: abi.enumValue('GhosttyRenderStateData', 'ROW_ITERATOR'),
317
+ rowDirtyData: abi.enumValue('GhosttyRenderStateRowData', 'DIRTY'),
318
+ rowCellsData: abi.enumValue('GhosttyRenderStateRowData', 'CELLS'),
319
+ rowIteratorNext: fn('ghostty_render_state_row_iterator_next'),
320
+ rowGet: fn('ghostty_render_state_row_get'),
321
+ rowCellsNext: fn('ghostty_render_state_row_cells_next'),
322
+ rowCellsGetMulti: fn('ghostty_render_state_row_cells_get_multi'),
323
+ };
324
+ cellLayouts.set(abi, layout);
325
+ return layout;
326
+ }
327
+
328
+ interface ExtractScratch {
329
+ base: number;
330
+ size: number;
331
+ slot: number;
332
+ flag: number;
333
+ keys: number;
334
+ values: number;
335
+ raw: number;
336
+ len: number;
337
+ style: number;
338
+ written: number;
339
+ }
340
+
341
+ function allocScratch(abi: Abi, layout: CellLayout): ExtractScratch {
342
+ const size = 8 + 8 + 12 + 12 + 8 + 8 + layout.styleSize + 8;
343
+ const base = abi.alloc(size);
344
+ const scratch: ExtractScratch = {
345
+ base,
346
+ size,
347
+ slot: base,
348
+ flag: base + 8,
349
+ keys: base + 16,
350
+ values: base + 28,
351
+ raw: base + 40,
352
+ len: base + 48,
353
+ style: base + 56,
354
+ written: base + 56 + layout.styleSize,
355
+ };
356
+ const view = abi.view();
357
+ const cells = 'GhosttyRenderStateRowCellsData';
358
+ view.setInt32(scratch.keys, abi.enumValue(cells, 'RAW'), true);
359
+ view.setInt32(scratch.keys + 4, abi.enumValue(cells, 'GRAPHEMES_LEN'), true);
360
+ view.setInt32(scratch.keys + 8, abi.enumValue(cells, 'STYLE'), true);
361
+ view.setUint32(scratch.values, scratch.raw, true);
362
+ view.setUint32(scratch.values + 4, scratch.len, true);
363
+ view.setUint32(scratch.values + 8, scratch.style, true);
364
+ return scratch;
237
365
  }
238
366
 
239
367
  /**
@@ -252,6 +380,8 @@ export class GhosttyTerminal {
252
380
  private _cols: number;
253
381
  private _rows: number;
254
382
 
383
+ private readonly layout: CellLayout;
384
+ private readonly scratch: ExtractScratch;
255
385
  private cellPool: GhosttyCell[] = [];
256
386
  private rowDirty: boolean[] = [];
257
387
  private dirty: DirtyState = DirtyState.FULL;
@@ -298,6 +428,8 @@ export class GhosttyTerminal {
298
428
  ) {
299
429
  this._cols = cols;
300
430
  this._rows = rows;
431
+ this.layout = cellLayout(abi);
432
+ this.scratch = allocScratch(abi, this.layout);
301
433
  this.handle = abi.newHandle('ghostty_terminal_new', (slot) =>
302
434
  abi.call('ghostty_terminal_new', 0, slot, cols, rows)
303
435
  );
@@ -366,6 +498,7 @@ export class GhosttyTerminal {
366
498
  abi.call('ghostty_render_state_row_cells_free', this.rowCells);
367
499
  abi.call('ghostty_render_state_row_iterator_free', this.rowIterator);
368
500
  abi.call('ghostty_render_state_free', this.renderState);
501
+ abi.free(this.scratch.base, this.scratch.size);
369
502
  abi.call('ghostty_terminal_free', this.handle);
370
503
  for (const index of this.callbacks) abi.removeCallback(index);
371
504
  this.callbacks = [];
@@ -899,113 +1032,116 @@ export class GhosttyTerminal {
899
1032
 
900
1033
  private extractRows(all: boolean): void {
901
1034
  const abi = this.abi;
902
- const rowData = 'GhosttyRenderStateRowData';
903
- abi.with(8, (slot) => {
904
- const view = abi.view();
905
- view.setUint32(slot, this.rowIterator, true);
906
- abi.check(
907
- 'ghostty_render_state_get',
908
- this.renderState,
909
- abi.enumValue('GhosttyRenderStateData', 'ROW_ITERATOR'),
910
- slot
911
- );
912
- abi.view().setUint32(slot, this.rowCells, true);
913
- let y = 0;
914
- while (
915
- y < this._rows &&
916
- abi.call('ghostty_render_state_row_iterator_next', this.rowIterator)
917
- ) {
918
- const rowDirty = abi.with(4, (out) => {
919
- abi.check(
920
- 'ghostty_render_state_row_get',
921
- this.rowIterator,
922
- abi.enumValue(rowData, 'DIRTY'),
923
- out
924
- );
925
- return abi.view().getUint8(out) !== 0;
926
- });
927
- if (all || rowDirty) {
928
- this.rowDirty[y] = true;
929
- abi.check(
930
- 'ghostty_render_state_row_get',
931
- this.rowIterator,
932
- abi.enumValue(rowData, 'CELLS'),
933
- slot
934
- );
935
- this.extractCells(y);
936
- }
937
- y++;
1035
+ const layout = this.layout;
1036
+ const rowGet = layout.rowGet;
1037
+ const iteratorNext = layout.rowIteratorNext;
1038
+ const scratch = this.scratch;
1039
+ const view = abi.view();
1040
+ view.setUint32(scratch.slot, this.rowIterator, true);
1041
+ abi.check('ghostty_render_state_get', this.renderState, layout.rowIteratorData, scratch.slot);
1042
+ let y = 0;
1043
+ while (y < this._rows && iteratorNext(this.rowIterator)) {
1044
+ const v = abi.view();
1045
+ v.setUint32(scratch.slot, this.rowCells, true);
1046
+ let rowDirty = all;
1047
+ if (!rowDirty) {
1048
+ rowGet(this.rowIterator, layout.rowDirtyData, scratch.flag);
1049
+ rowDirty = abi.view().getUint8(scratch.flag) !== 0;
938
1050
  }
939
- });
1051
+ if (rowDirty) {
1052
+ this.rowDirty[y] = true;
1053
+ const result = rowGet(this.rowIterator, layout.rowCellsData, scratch.slot);
1054
+ if (result !== GHOSTTY_SUCCESS)
1055
+ throw new GhosttyCallError('ghostty_render_state_row_get', result);
1056
+ this.extractCells(y);
1057
+ }
1058
+ y++;
1059
+ }
940
1060
  }
941
1061
 
942
1062
  private extractCells(y: number): void {
943
1063
  const abi = this.abi;
944
- const cellsData = 'GhosttyRenderStateRowCellsData';
945
- const styleSize = abi.sizeOf('GhosttyStyle');
946
- const keyRaw = abi.enumValue(cellsData, 'RAW');
947
- const keyLen = abi.enumValue(cellsData, 'GRAPHEMES_LEN');
948
- const keyStyle = abi.enumValue(cellsData, 'STYLE');
949
- const block = 3 * 4 + 3 * 4 + 8 + 8 + styleSize + 8;
950
- abi.with(block, (base) => {
951
- const keys = base;
952
- const values = base + 12;
953
- const raw = base + 24;
954
- const len = base + 32;
955
- const style = base + 40;
956
- const written = style + styleSize;
957
- const view = abi.view();
958
- view.setInt32(keys, keyRaw, true);
959
- view.setInt32(keys + 4, keyLen, true);
960
- view.setInt32(keys + 8, keyStyle, true);
961
- view.setUint32(values, raw, true);
962
- view.setUint32(values + 4, len, true);
963
- view.setUint32(values + 8, style, true);
964
- let x = 0;
965
- let hyperlinkRun = 0;
966
- let previousLinked = false;
967
- while (x < this._cols && abi.call('ghostty_render_state_row_cells_next', this.rowCells)) {
968
- abi.view().setUint32(style, styleSize, true);
969
- abi.check(
970
- 'ghostty_render_state_row_cells_get_multi',
971
- this.rowCells,
972
- 3,
973
- keys,
974
- values,
975
- written
976
- );
977
- const v = abi.view();
978
- const cell = this.cellPool[y * this._cols + x];
979
- const graphemeLen = v.getUint32(len, true);
980
- this.fillCell(cell, v.getBigUint64(raw, true), style, graphemeLen);
981
- if (cell.hyperlink_id) {
982
- if (!previousLinked) hyperlinkRun++;
983
- cell.hyperlink_id = hyperlinkRun;
984
- }
985
- previousLinked = cell.hyperlink_id !== 0;
986
- x++;
1064
+ const layout = this.layout;
1065
+ const scratch = this.scratch;
1066
+ const cellsNext = layout.rowCellsNext;
1067
+ const getMulti = layout.rowCellsGetMulti;
1068
+ const pool = this.cellPool;
1069
+ const cols = this._cols;
1070
+ const rowStart = y * cols;
1071
+ let view = abi.view();
1072
+ let x = 0;
1073
+ let hyperlinkRun = 0;
1074
+ let previousLinked = false;
1075
+ while (x < cols && cellsNext(this.rowCells)) {
1076
+ if (view.buffer !== abi.exports.memory.buffer) view = abi.view();
1077
+ view.setUint32(scratch.style, layout.styleSize, true);
1078
+ const result = getMulti(this.rowCells, 3, scratch.keys, scratch.values, scratch.written);
1079
+ if (result !== GHOSTTY_SUCCESS) {
1080
+ throw new GhosttyCallError('ghostty_render_state_row_cells_get_multi', result);
987
1081
  }
988
- });
1082
+ if (view.buffer !== abi.exports.memory.buffer) view = abi.view();
1083
+ const cell = pool[rowStart + x];
1084
+ this.fillCellFrom(
1085
+ cell,
1086
+ view,
1087
+ view.getUint32(scratch.raw, true),
1088
+ view.getUint32(scratch.raw + 4, true),
1089
+ scratch.style,
1090
+ view.getUint32(scratch.len, true)
1091
+ );
1092
+ if (cell.hyperlink_id) {
1093
+ if (!previousLinked) hyperlinkRun++;
1094
+ cell.hyperlink_id = hyperlinkRun;
1095
+ }
1096
+ previousLinked = cell.hyperlink_id !== 0;
1097
+ x++;
1098
+ }
989
1099
  }
990
1100
 
991
1101
  private fillCell(cell: GhosttyCell, raw: bigint, stylePtr: number, graphemeLen: number): void {
992
- const abi = this.abi;
993
- const view = abi.view();
994
- const tag = bitsOf(raw, abi.bits('GhosttyCell', 'content_tag'));
995
- const content = abi.bits('GhosttyCell', 'content');
996
- const contentBits = bitsOf(raw, content);
997
- const tags = 'GhosttyCellContentTag';
998
- const isText =
999
- tag === abi.enumValue(tags, 'CODEPOINT') || tag === abi.enumValue(tags, 'CODEPOINT_GRAPHEME');
1000
- cell.codepoint = isText ? contentBits & 0x1fffff : 0;
1001
- cell.grapheme_len = Math.max(0, graphemeLen - 1);
1002
-
1003
- const at = (f: string) => stylePtr + abi.offset('GhosttyStyle', f);
1004
- const fg = this.styleColor(view, at('fg_color')) ?? this.colors.foreground;
1005
- let bg = this.styleColor(view, at('bg_color')) ?? this.colors.background;
1006
- if (tag === abi.enumValue(tags, 'BG_COLOR_PALETTE')) {
1102
+ this.fillCellFrom(
1103
+ cell,
1104
+ this.abi.view(),
1105
+ Number(raw & 0xffffffffn),
1106
+ Number(raw >> 32n),
1107
+ stylePtr,
1108
+ graphemeLen
1109
+ );
1110
+ }
1111
+
1112
+ private fillCellFrom(
1113
+ cell: GhosttyCell,
1114
+ view: DataView,
1115
+ lo: number,
1116
+ hi: number,
1117
+ stylePtr: number,
1118
+ graphemeLen: number
1119
+ ): void {
1120
+ const L = this.layout;
1121
+ const tag = lo & 0x3;
1122
+ const contentBits = (lo >>> 2) & 0xffffff;
1123
+ cell.codepoint = tag === L.tagCodepoint || tag === L.tagGrapheme ? contentBits & 0x1fffff : 0;
1124
+ cell.grapheme_len = graphemeLen > 1 ? graphemeLen - 1 : 0;
1125
+
1126
+ const styleId = ((lo >>> 26) | (hi << 6)) & 0xffff;
1127
+ let fg = this.colors.foreground;
1128
+ let bg = this.colors.background;
1129
+ let flags = 0;
1130
+ if (styleId !== 0) {
1131
+ fg = this.styleColor(view, stylePtr + L.fgColor) ?? fg;
1132
+ bg = this.styleColor(view, stylePtr + L.bgColor) ?? bg;
1133
+ if (view.getUint8(stylePtr + L.bold)) flags |= CellFlags.BOLD;
1134
+ if (view.getUint8(stylePtr + L.italic)) flags |= CellFlags.ITALIC;
1135
+ if (view.getInt32(stylePtr + L.underline, true) !== 0) flags |= CellFlags.UNDERLINE;
1136
+ if (view.getUint8(stylePtr + L.strikethrough)) flags |= CellFlags.STRIKETHROUGH;
1137
+ if (view.getUint8(stylePtr + L.inverse)) flags |= CellFlags.INVERSE;
1138
+ if (view.getUint8(stylePtr + L.invisible)) flags |= CellFlags.INVISIBLE;
1139
+ if (view.getUint8(stylePtr + L.blink)) flags |= CellFlags.BLINK;
1140
+ if (view.getUint8(stylePtr + L.faint)) flags |= CellFlags.FAINT;
1141
+ }
1142
+ if (tag === L.tagBgPalette) {
1007
1143
  bg = this.colors.palette[contentBits & 0xff] ?? bg;
1008
- } else if (tag === abi.enumValue(tags, 'BG_COLOR_RGB')) {
1144
+ } else if (tag === L.tagBgRgb) {
1009
1145
  bg = { r: contentBits & 0xff, g: (contentBits >> 8) & 0xff, b: (contentBits >> 16) & 0xff };
1010
1146
  }
1011
1147
  cell.fg_r = fg.r;
@@ -1014,37 +1150,19 @@ export class GhosttyTerminal {
1014
1150
  cell.bg_r = bg.r;
1015
1151
  cell.bg_g = bg.g;
1016
1152
  cell.bg_b = bg.b;
1017
-
1018
- let flags = 0;
1019
- if (view.getUint8(at('bold'))) flags |= CellFlags.BOLD;
1020
- if (view.getUint8(at('italic'))) flags |= CellFlags.ITALIC;
1021
- if (view.getInt32(at('underline'), true) !== 0) flags |= CellFlags.UNDERLINE;
1022
- if (view.getUint8(at('strikethrough'))) flags |= CellFlags.STRIKETHROUGH;
1023
- if (view.getUint8(at('inverse'))) flags |= CellFlags.INVERSE;
1024
- if (view.getUint8(at('invisible'))) flags |= CellFlags.INVISIBLE;
1025
- if (view.getUint8(at('blink'))) flags |= CellFlags.BLINK;
1026
- if (view.getUint8(at('faint'))) flags |= CellFlags.FAINT;
1027
1153
  cell.flags = flags;
1028
1154
 
1029
- const wide = bitsOf(raw, abi.bits('GhosttyCell', 'wide'));
1030
- const wides = 'GhosttyCellWide';
1031
- cell.width =
1032
- wide === abi.enumValue(wides, 'WIDE')
1033
- ? 2
1034
- : wide === abi.enumValue(wides, 'SPACER_TAIL')
1035
- ? 0
1036
- : 1;
1037
- cell.hyperlink_id = bitsOf(raw, abi.bits('GhosttyCell', 'hyperlink'));
1155
+ const wide = (hi >>> 10) & 0x3;
1156
+ cell.width = wide === L.wideWide ? 2 : wide === L.wideSpacerTail ? 0 : 1;
1157
+ cell.hyperlink_id = (hi >>> 13) & 0x1;
1038
1158
  }
1039
1159
 
1040
1160
  private styleColor(view: DataView, ptr: number): RGB | null {
1041
- const abi = this.abi;
1042
- const tag = view.getInt32(ptr + abi.offset('GhosttyStyleColor', 'tag'), true);
1043
- const value = ptr + abi.offset('GhosttyStyleColor', 'value');
1044
- if (tag === abi.enumValue('GhosttyStyleColorTag', 'PALETTE')) {
1045
- return this.colors.palette[view.getUint8(value)] ?? null;
1046
- }
1047
- if (tag === abi.enumValue('GhosttyStyleColorTag', 'RGB')) return rgbAt(view, value);
1161
+ const L = this.layout;
1162
+ const tag = view.getInt32(ptr + L.colorTag, true);
1163
+ if (tag === L.colorPalette)
1164
+ return this.colors.palette[view.getUint8(ptr + L.colorValue)] ?? null;
1165
+ if (tag === L.colorRgb) return rgbAt(view, ptr + L.colorValue);
1048
1166
  return null;
1049
1167
  }
1050
1168
 
package/lib/index.ts CHANGED
@@ -92,7 +92,8 @@ export { Key, KeyAction, Mods } from './types';
92
92
  export type { KeyEvent, GhosttyCell, RGB, Cursor, TerminalHandle } from './types';
93
93
 
94
94
  // Low-level components (for custom integrations)
95
- export { CanvasRenderer } from './renderer';
95
+ export { CanvasRenderer, TerminalRenderer } from './renderer';
96
+ export { WebglRenderer } from './webgl-renderer';
96
97
  export type { RendererOptions, FontMetrics, IRenderable } from './renderer';
97
98
  export { InputHandler } from './input-handler';
98
99
  export { EventEmitter } from './event-emitter';
@@ -9,7 +9,6 @@
9
9
  * - Emitting data for Terminal to send to PTY
10
10
  *
11
11
  * Limitations:
12
- * - Does not handle IME/composition events (CJK input) - to be added later
13
12
  * - Captures all keyboard input (preventDefault on everything)
14
13
  */
15
14
 
@@ -186,6 +185,7 @@ export class InputHandler {
186
185
  private keypressListener: ((e: KeyboardEvent) => void) | null = null;
187
186
  private pasteListener: ((e: ClipboardEvent) => void) | null = null;
188
187
  private beforeInputListener: ((e: InputEvent) => void) | null = null;
188
+ private containerBeforeInputListener: ((e: InputEvent) => void) | null = null;
189
189
  private compositionStartListener: ((e: CompositionEvent) => void) | null = null;
190
190
  private compositionUpdateListener: ((e: CompositionEvent) => void) | null = null;
191
191
  private compositionEndListener: ((e: CompositionEvent) => void) | null = null;
@@ -286,6 +286,13 @@ export class InputHandler {
286
286
  this.inputElement.addEventListener('beforeinput', this.beforeInputListener);
287
287
  }
288
288
 
289
+ if (this.inputElement !== this.container) {
290
+ this.containerBeforeInputListener = (event: InputEvent) => {
291
+ if (event.target === this.container) this.handleBeforeInput(event);
292
+ };
293
+ this.container.addEventListener('beforeinput', this.containerBeforeInputListener);
294
+ }
295
+
289
296
  this.compositionStartListener = this.handleCompositionStart.bind(this);
290
297
  this.container.addEventListener('compositionstart', this.compositionStartListener);
291
298
 
@@ -1057,6 +1064,11 @@ export class InputHandler {
1057
1064
  this.beforeInputListener = null;
1058
1065
  }
1059
1066
 
1067
+ if (this.containerBeforeInputListener) {
1068
+ this.container.removeEventListener('beforeinput', this.containerBeforeInputListener);
1069
+ this.containerBeforeInputListener = null;
1070
+ }
1071
+
1060
1072
  if (this.compositionStartListener) {
1061
1073
  this.container.removeEventListener('compositionstart', this.compositionStartListener);
1062
1074
  this.compositionStartListener = null;
package/lib/interfaces.ts CHANGED
@@ -14,6 +14,12 @@ export interface ITerminalOptions {
14
14
  fontSize?: number; // Default: 15
15
15
  fontFamily?: string; // Default: 'monospace'
16
16
  allowTransparency?: boolean;
17
+ /**
18
+ * How the terminal draws, read when it opens. 'webgl' draws on the GPU and
19
+ * falls back to 'canvas' when WebGL 2 is unavailable or its context is lost.
20
+ * Default: 'webgl'.
21
+ */
22
+ renderer?: 'webgl' | 'canvas';
17
23
 
18
24
  // Phase 1 additions
19
25
  convertEol?: boolean; // Convert \n to \r\n (default: false)