@aurigax-ai/ghostty-web 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/lib/box-glyphs.ts +278 -0
- package/lib/ghostty.ts +244 -126
- package/lib/index.ts +2 -1
- package/lib/input-handler.ts +13 -1
- package/lib/interfaces.ts +6 -0
- package/lib/renderer.ts +550 -688
- package/lib/selection-manager.ts +8 -3
- package/lib/terminal.ts +58 -30
- package/lib/webgl-renderer.ts +870 -0
- package/package.json +2 -2
package/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
|
-
|
|
236
|
-
|
|
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
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
);
|
|
912
|
-
|
|
913
|
-
let
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
abi.
|
|
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
|
|
945
|
-
const
|
|
946
|
-
const
|
|
947
|
-
const
|
|
948
|
-
const
|
|
949
|
-
const
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
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
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
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 ===
|
|
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 =
|
|
1030
|
-
|
|
1031
|
-
cell.
|
|
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
|
|
1042
|
-
const tag = view.getInt32(ptr +
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
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';
|
package/lib/input-handler.ts
CHANGED
|
@@ -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)
|