@zem-sh/terminal 0.1.2 → 0.1.4
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/assets/terminal.wasm +0 -0
- package/package.json +1 -1
- package/src/index.ts +10 -1
- package/src/terminal/input/keyboard/keymap/kitty.ts +13 -0
- package/src/terminal/runtime/create-runtime.ts +96 -5
- package/src/terminal/runtime/font-resource-store.ts +73 -36
- package/src/terminal/runtime/session.ts +17 -2
- package/src/terminal/runtime/types.ts +10 -0
- package/src/terminal/wasm/metadata.ts +11 -0
- package/src/terminal/wasm/runtime/terminal-wasm.ts +22 -5
- package/src/terminal/wasm/runtime/types.ts +5 -0
- package/src/terminal/wasm/wasm-asset.ts +10 -0
- package/src/terminal/wasm/embedded.ts +0 -11596
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zem-sh/terminal",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Terminal emulator core — the Ghostty VT engine compiled to WebAssembly, GPU text rendering, ligature shaping, themes and font fallback",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/src/index.ts
CHANGED
|
@@ -31,8 +31,17 @@ export type {
|
|
|
31
31
|
TerminalShaderStageMode,
|
|
32
32
|
TerminalShaderStageBackend,
|
|
33
33
|
TerminalShaderStageSource,
|
|
34
|
+
TerminalAppSession,
|
|
34
35
|
} from "./terminal/runtime/types"
|
|
35
36
|
|
|
37
|
+
export {
|
|
38
|
+
createTerminalAppSession,
|
|
39
|
+
getDefaultTerminalAppSession,
|
|
40
|
+
prewarmTerminalCore,
|
|
41
|
+
} from "./terminal/runtime/session"
|
|
42
|
+
export type { CreateTerminalAppSessionOptions } from "./terminal/runtime/session"
|
|
43
|
+
export { TERMINAL_WASM_URL } from "./terminal/wasm/wasm-asset"
|
|
44
|
+
|
|
36
45
|
export {
|
|
37
46
|
shaderStagesForPreset,
|
|
38
47
|
shaderStagesForPresets,
|
|
@@ -56,7 +65,7 @@ export {
|
|
|
56
65
|
TERMINAL_WASM_OPTIMIZE,
|
|
57
66
|
TERMINAL_WASM_SIZE,
|
|
58
67
|
TERMINAL_WASM_SHA256,
|
|
59
|
-
} from "./terminal/wasm/
|
|
68
|
+
} from "./terminal/wasm/metadata"
|
|
60
69
|
|
|
61
70
|
// Theme APIs
|
|
62
71
|
export type { ThemeColor, ThemeTerminalColor, GhosttyTheme } from "./theme/ghostty"
|
|
@@ -143,6 +143,19 @@ function encodeKittySequence(
|
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
export function encodeKittyKeyEvent(event: KeyboardEvent, kittyFlags: number): string {
|
|
146
|
+
// Preserve the conventional terminal interrupt byte even when an app has
|
|
147
|
+
// enabled Kitty keyboard reporting. Older TTY programs may echo CSI-u input.
|
|
148
|
+
if (
|
|
149
|
+
event.type === "keydown" &&
|
|
150
|
+
event.ctrlKey &&
|
|
151
|
+
!event.shiftKey &&
|
|
152
|
+
!event.altKey &&
|
|
153
|
+
!event.metaKey &&
|
|
154
|
+
event.key?.toLowerCase() === "c"
|
|
155
|
+
) {
|
|
156
|
+
return "\x03";
|
|
157
|
+
}
|
|
158
|
+
|
|
146
159
|
const reportEvents = (kittyFlags & KITTY_FLAG_REPORT_EVENTS) !== 0;
|
|
147
160
|
const reportAlternate = (kittyFlags & KITTY_FLAG_REPORT_ALTERNATE) !== 0;
|
|
148
161
|
const reportAll = (kittyFlags & KITTY_FLAG_REPORT_ALL) !== 0;
|
|
@@ -119,7 +119,8 @@ import {
|
|
|
119
119
|
type RuntimeAppApiRuntime,
|
|
120
120
|
type RuntimeAppApiSharedState,
|
|
121
121
|
} from "./create-runtime/runtime-app-api";
|
|
122
|
-
export { createTerminalAppSession, getDefaultTerminalAppSession } from "./session";
|
|
122
|
+
export { createTerminalAppSession, getDefaultTerminalAppSession, prewarmTerminalCore } from "./session";
|
|
123
|
+
export type { CreateTerminalAppSessionOptions } from "./session";
|
|
123
124
|
export type {
|
|
124
125
|
TerminalAppElements,
|
|
125
126
|
TerminalAppCallbacks,
|
|
@@ -740,6 +741,10 @@ export function createTerminalApp(options: TerminalAppOptions): TerminalApp {
|
|
|
740
741
|
let fontPromise: Promise<void> | null = null;
|
|
741
742
|
let fontError: Error | null = null;
|
|
742
743
|
let fontLease: TerminalFontResourceLease | null = null;
|
|
744
|
+
let deferredFontLease: TerminalFontResourceLease | null = null;
|
|
745
|
+
// Bumped whenever the configured sources change so a late (deferred or
|
|
746
|
+
// superseded) acquisition cannot install fonts into the new generation.
|
|
747
|
+
let fontGeneration = 0;
|
|
743
748
|
|
|
744
749
|
setShaderStages(options.shaderStages ?? []);
|
|
745
750
|
|
|
@@ -866,9 +871,14 @@ export function createTerminalApp(options: TerminalAppOptions): TerminalApp {
|
|
|
866
871
|
}
|
|
867
872
|
|
|
868
873
|
function releaseFontLease() {
|
|
869
|
-
if (
|
|
870
|
-
|
|
871
|
-
|
|
874
|
+
if (fontLease) {
|
|
875
|
+
fontLease.release();
|
|
876
|
+
fontLease = null;
|
|
877
|
+
}
|
|
878
|
+
if (deferredFontLease) {
|
|
879
|
+
deferredFontLease.release();
|
|
880
|
+
deferredFontLease = null;
|
|
881
|
+
}
|
|
872
882
|
}
|
|
873
883
|
|
|
874
884
|
function clearFontRuntimeState() {
|
|
@@ -886,6 +896,7 @@ export function createTerminalApp(options: TerminalAppOptions): TerminalApp {
|
|
|
886
896
|
|
|
887
897
|
async function setFontSources(sources: TerminalFontSource[]) {
|
|
888
898
|
configuredFontSources = normalizeFontSources(sources, undefined);
|
|
899
|
+
fontGeneration += 1;
|
|
889
900
|
fontPromise = null;
|
|
890
901
|
fontError = null;
|
|
891
902
|
releaseFontLease();
|
|
@@ -949,15 +960,80 @@ export function createTerminalApp(options: TerminalAppOptions): TerminalApp {
|
|
|
949
960
|
appendLog(`[ui] line height ${next.toFixed(2)}`);
|
|
950
961
|
}
|
|
951
962
|
|
|
963
|
+
/**
|
|
964
|
+
* Append late-loaded fallback faces (heavy symbol/emoji/CJK fonts marked
|
|
965
|
+
* `defer`) to the live font chain. Appending is safe: indices of the faces
|
|
966
|
+
* already installed do not move, and only the glyph-pick cache needs to be
|
|
967
|
+
* invalidated so previously-missing glyphs are re-resolved against the new
|
|
968
|
+
* entries on the next frame.
|
|
969
|
+
*/
|
|
970
|
+
async function loadDeferredFonts(sources: TerminalFontSource[], generation: number) {
|
|
971
|
+
let lease: TerminalFontResourceLease | null = null;
|
|
972
|
+
try {
|
|
973
|
+
lease = await fontResourceStore.acquire(sources);
|
|
974
|
+
} catch {
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
if (!lease.faces.length) {
|
|
978
|
+
lease.release();
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
981
|
+
if (generation !== fontGeneration || !fontState.font) {
|
|
982
|
+
lease.release();
|
|
983
|
+
return;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
releaseDeferredFontLease();
|
|
987
|
+
deferredFontLease = lease;
|
|
988
|
+
|
|
989
|
+
const entries: FontEntry[] = [];
|
|
990
|
+
for (let i = 0; i < lease.faces.length; i += 1) {
|
|
991
|
+
const face = lease.faces[i];
|
|
992
|
+
entries.push(createFontEntry(face.font, face.label));
|
|
993
|
+
}
|
|
994
|
+
if (!entries.length) return;
|
|
995
|
+
|
|
996
|
+
fontState.fonts.push(...entries);
|
|
997
|
+
fontState.fontPickCache.clear();
|
|
998
|
+
needsRender = true;
|
|
999
|
+
appendLog(`[ui] deferred fallback fonts loaded (+${entries.length})`);
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
function releaseDeferredFontLease() {
|
|
1003
|
+
if (!deferredFontLease) return;
|
|
1004
|
+
deferredFontLease.release();
|
|
1005
|
+
deferredFontLease = null;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
952
1008
|
async function ensureFont() {
|
|
953
1009
|
if (fontState.font || fontPromise) return fontPromise;
|
|
954
1010
|
fontPromise = (async () => {
|
|
1011
|
+
const generation = fontGeneration;
|
|
1012
|
+
const configured = configuredFontSources;
|
|
1013
|
+
// Heavy fallback faces opt out of the critical path: paint with the
|
|
1014
|
+
// primary chain first, then append them in the background. If every
|
|
1015
|
+
// source is deferred (or none is), there is nothing to defer — load the
|
|
1016
|
+
// whole list up front.
|
|
1017
|
+
const deferredSources = configured.filter((source) => source.defer);
|
|
1018
|
+
const eagerSources = configured.filter((source) => !source.defer);
|
|
1019
|
+
const initialSources = eagerSources.length ? eagerSources : configured;
|
|
1020
|
+
|
|
955
1021
|
let acquiredLease: TerminalFontResourceLease | null = null;
|
|
956
1022
|
try {
|
|
957
|
-
acquiredLease = await fontResourceStore.acquire(
|
|
1023
|
+
acquiredLease = await fontResourceStore.acquire(initialSources);
|
|
1024
|
+
if (generation !== fontGeneration) {
|
|
1025
|
+
acquiredLease.release();
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
958
1028
|
if (!acquiredLease.faces.length) {
|
|
959
1029
|
acquiredLease.release();
|
|
960
1030
|
acquiredLease = await fontResourceStore.acquire(FALLBACK_LOCAL_FONT_SOURCES);
|
|
1031
|
+
// A newer font selection superseded this load while the fallback
|
|
1032
|
+
// system fonts were being acquired.
|
|
1033
|
+
if (generation !== fontGeneration) {
|
|
1034
|
+
acquiredLease.release();
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
961
1037
|
}
|
|
962
1038
|
if (!acquiredLease.faces.length) {
|
|
963
1039
|
throw new Error("Unable to load any configured font source.");
|
|
@@ -988,12 +1064,26 @@ export function createTerminalApp(options: TerminalAppOptions): TerminalApp {
|
|
|
988
1064
|
log("font loaded");
|
|
989
1065
|
}
|
|
990
1066
|
updateGrid();
|
|
1067
|
+
|
|
1068
|
+
// Start the deferred faces only after the first frame has had a
|
|
1069
|
+
// chance to paint; they are usually large font files.
|
|
1070
|
+
if (deferredSources.length && eagerSources.length) {
|
|
1071
|
+
const startDeferred = () => {
|
|
1072
|
+
void loadDeferredFonts(deferredSources, generation);
|
|
1073
|
+
};
|
|
1074
|
+
if (typeof requestAnimationFrame === "function") {
|
|
1075
|
+
requestAnimationFrame(startDeferred);
|
|
1076
|
+
} else {
|
|
1077
|
+
setTimeout(startDeferred, 16);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
991
1080
|
} catch (err) {
|
|
992
1081
|
if (acquiredLease) {
|
|
993
1082
|
acquiredLease.release();
|
|
994
1083
|
} else {
|
|
995
1084
|
releaseFontLease();
|
|
996
1085
|
}
|
|
1086
|
+
if (generation !== fontGeneration) return;
|
|
997
1087
|
clearFontRuntimeState();
|
|
998
1088
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
999
1089
|
fontError = error;
|
|
@@ -1005,6 +1095,7 @@ export function createTerminalApp(options: TerminalAppOptions): TerminalApp {
|
|
|
1005
1095
|
}
|
|
1006
1096
|
|
|
1007
1097
|
cleanupFns.push(() => {
|
|
1098
|
+
fontGeneration += 1;
|
|
1008
1099
|
releaseFontLease();
|
|
1009
1100
|
clearFontRuntimeState();
|
|
1010
1101
|
configuredFontSources = [];
|
|
@@ -268,9 +268,27 @@ function createUrlByteCache(
|
|
|
268
268
|
};
|
|
269
269
|
}
|
|
270
270
|
|
|
271
|
+
type LocalFontQuery = () => Promise<LocalFontFaceData[]>;
|
|
272
|
+
|
|
273
|
+
/** Resolve the Local Font Access query function once per environment. */
|
|
274
|
+
function resolveLocalFontQuery(): LocalFontQuery | null {
|
|
275
|
+
const globalAccess = globalThis as GlobalWithLocalFontAccess;
|
|
276
|
+
const nav = (globalAccess.navigator ??
|
|
277
|
+
(typeof navigator !== "undefined" ? navigator : undefined)) as NavigatorWithLocalFontAccess;
|
|
278
|
+
if (!nav) return null;
|
|
279
|
+
if (typeof globalAccess.queryLocalFonts === "function") {
|
|
280
|
+
return () => globalAccess.queryLocalFonts!();
|
|
281
|
+
}
|
|
282
|
+
if (typeof nav.queryLocalFonts === "function") {
|
|
283
|
+
return () => nav.queryLocalFonts!();
|
|
284
|
+
}
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
|
|
271
288
|
async function tryLoadLocalFontBuffer(
|
|
272
289
|
matchers: string[],
|
|
273
290
|
label: string,
|
|
291
|
+
queryFonts?: LocalFontQuery,
|
|
274
292
|
): Promise<ArrayBuffer | null> {
|
|
275
293
|
const normalizedMatchers = matchers.map((matcher) => matcher.toLowerCase()).filter(Boolean);
|
|
276
294
|
if (!normalizedMatchers.length) return null;
|
|
@@ -279,13 +297,8 @@ async function tryLoadLocalFontBuffer(
|
|
|
279
297
|
const nav = (globalAccess.navigator ??
|
|
280
298
|
(typeof navigator !== "undefined" ? navigator : undefined)) as NavigatorWithLocalFontAccess;
|
|
281
299
|
if (!nav) return null;
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
? globalAccess.queryLocalFonts.bind(globalAccess)
|
|
285
|
-
: typeof nav.queryLocalFonts === "function"
|
|
286
|
-
? nav.queryLocalFonts.bind(nav)
|
|
287
|
-
: null;
|
|
288
|
-
if (!queryLocalFonts) return null;
|
|
300
|
+
const loadFonts = queryFonts ?? resolveLocalFontQuery();
|
|
301
|
+
if (!loadFonts) return null;
|
|
289
302
|
|
|
290
303
|
const sourceHint = detectStyleHint(`${label} ${normalizedMatchers.join(" ")}`);
|
|
291
304
|
const queryPermission = nav.permissions?.query;
|
|
@@ -299,7 +312,7 @@ async function tryLoadLocalFontBuffer(
|
|
|
299
312
|
}
|
|
300
313
|
|
|
301
314
|
try {
|
|
302
|
-
const fonts = await
|
|
315
|
+
const fonts = await loadFonts();
|
|
303
316
|
const matches = fonts.filter((font) => {
|
|
304
317
|
const name =
|
|
305
318
|
`${font.family ?? ""} ${font.fullName ?? ""} ${font.postscriptName ?? ""}`.toLowerCase();
|
|
@@ -393,6 +406,16 @@ export function createTerminalFontResourceStore(
|
|
|
393
406
|
const leaseSources = new Map<number, Map<string, number>>();
|
|
394
407
|
let totalCachedSourceBytes = 0;
|
|
395
408
|
let nextLeaseId = 1;
|
|
409
|
+
// Enumerating installed fonts is expensive and the same list serves every
|
|
410
|
+
// local matcher. Since sources now load concurrently, share one query per
|
|
411
|
+
// store instead of issuing one per source.
|
|
412
|
+
let localFontListPromise: Promise<LocalFontFaceData[]> | null = null;
|
|
413
|
+
const queryLocalFontList = (): Promise<LocalFontFaceData[]> => {
|
|
414
|
+
if (localFontListPromise) return localFontListPromise;
|
|
415
|
+
const query = resolveLocalFontQuery();
|
|
416
|
+
localFontListPromise = query ? query().catch(() => []) : Promise.resolve([]);
|
|
417
|
+
return localFontListPromise;
|
|
418
|
+
};
|
|
396
419
|
|
|
397
420
|
const removeSourceEntry = (key: string) => {
|
|
398
421
|
const entry = sourceCache.get(key);
|
|
@@ -446,6 +469,7 @@ export function createTerminalFontResourceStore(
|
|
|
446
469
|
return await tryLoadLocalFontBuffer(
|
|
447
470
|
matchers,
|
|
448
471
|
source.label ?? source.matchers[0] ?? "local-font",
|
|
472
|
+
queryLocalFontList,
|
|
449
473
|
);
|
|
450
474
|
}
|
|
451
475
|
return await loadUrlBuffer(sourceKey, normalizeUrlKey(source.url));
|
|
@@ -552,42 +576,55 @@ export function createTerminalFontResourceStore(
|
|
|
552
576
|
};
|
|
553
577
|
|
|
554
578
|
const acquire = async (sources: TerminalFontSource[]): Promise<TerminalFontResourceLease> => {
|
|
555
|
-
|
|
556
|
-
|
|
579
|
+
// Load and parse every source concurrently. The store already dedupes
|
|
580
|
+
// in-flight loads/parses; awaiting them one at a time serialized each
|
|
581
|
+
// network fetch and parse, which dominated first paint on a cold cache.
|
|
582
|
+
// `Promise.all` preserves source order, and the ordered chain matters:
|
|
583
|
+
// primary face first, fallbacks after.
|
|
584
|
+
const loaded = await Promise.all(
|
|
585
|
+
sources.map(async (source, sourceIndex) => {
|
|
586
|
+
const sourceKey = resolveSourceKey(source);
|
|
587
|
+
const baseLabel = sourceBaseLabel(source, sourceIndex);
|
|
588
|
+
let buffer: ArrayBuffer | null;
|
|
589
|
+
try {
|
|
590
|
+
buffer = await loadSourceBufferCached(source, sourceKey);
|
|
591
|
+
} catch {
|
|
592
|
+
buffer = null;
|
|
593
|
+
}
|
|
557
594
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
}
|
|
595
|
+
if (!buffer) {
|
|
596
|
+
if (source.type === "local") {
|
|
597
|
+
const prefix = source.required
|
|
598
|
+
? "required local font missing"
|
|
599
|
+
: "optional local font missing";
|
|
600
|
+
console.warn(`[font] ${prefix} (${source.matchers.join(", ")})`);
|
|
601
|
+
}
|
|
602
|
+
return null;
|
|
603
|
+
}
|
|
568
604
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
console.warn(`[font] ${prefix} (${source.matchers.join(", ")})`);
|
|
605
|
+
let parsedFaces: ParsedFontFace[];
|
|
606
|
+
try {
|
|
607
|
+
parsedFaces = await parseSourceFacesCached(sourceKey, buffer);
|
|
608
|
+
} catch {
|
|
609
|
+
parsedFaces = [];
|
|
575
610
|
}
|
|
576
|
-
|
|
577
|
-
|
|
611
|
+
if (!parsedFaces.length) return null;
|
|
612
|
+
return { sourceKey, baseLabel, parsedFaces };
|
|
613
|
+
}),
|
|
614
|
+
);
|
|
578
615
|
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
616
|
+
const faces: TerminalFontResourceFace[] = [];
|
|
617
|
+
const sourceRefs = new Map<string, number>();
|
|
618
|
+
const usedAt = now();
|
|
619
|
+
for (let sourceIndex = 0; sourceIndex < loaded.length; sourceIndex += 1) {
|
|
620
|
+
const result = loaded[sourceIndex];
|
|
621
|
+
if (!result) continue;
|
|
622
|
+
const { sourceKey, baseLabel, parsedFaces } = result;
|
|
586
623
|
|
|
587
624
|
const sourceEntry = sourceCache.get(sourceKey);
|
|
588
625
|
if (sourceEntry) {
|
|
589
626
|
sourceEntry.refCount += 1;
|
|
590
|
-
sourceEntry.lastUsedAt =
|
|
627
|
+
sourceEntry.lastUsedAt = usedAt;
|
|
591
628
|
}
|
|
592
629
|
sourceRefs.set(sourceKey, (sourceRefs.get(sourceKey) ?? 0) + 1);
|
|
593
630
|
|
|
@@ -3,12 +3,19 @@ import { loadTerminalWasm, type TerminalWasm } from "../wasm";
|
|
|
3
3
|
import { createTerminalFontResourceStore } from "./font-resource-store";
|
|
4
4
|
import type { TerminalAppSession, TerminalWasmLogListener } from "./types";
|
|
5
5
|
|
|
6
|
+
export type CreateTerminalAppSessionOptions = {
|
|
7
|
+
/** URL of the `terminal.wasm` asset; defaults to the bundled package asset. */
|
|
8
|
+
wasmUrl?: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
6
11
|
/**
|
|
7
12
|
* Create a new app session that lazily loads the WASM module and
|
|
8
13
|
* initializes the WebGPU core on first use. Multiple panes can
|
|
9
14
|
* share a single session to avoid duplicate resource loading.
|
|
10
15
|
*/
|
|
11
|
-
export function createTerminalAppSession(
|
|
16
|
+
export function createTerminalAppSession(
|
|
17
|
+
options: CreateTerminalAppSessionOptions = {},
|
|
18
|
+
): TerminalAppSession {
|
|
12
19
|
let wasmPromise: Promise<TerminalWasm> | null = null;
|
|
13
20
|
let webgpuCorePromise: Promise<WebGPUCoreState | null> | null = null;
|
|
14
21
|
const wasmLogListeners = new Set<TerminalWasmLogListener>();
|
|
@@ -23,7 +30,7 @@ export function createTerminalAppSession(): TerminalAppSession {
|
|
|
23
30
|
return {
|
|
24
31
|
getWasm: () => {
|
|
25
32
|
if (!wasmPromise) {
|
|
26
|
-
wasmPromise = loadTerminalWasm({ log: forwardWasmLog });
|
|
33
|
+
wasmPromise = loadTerminalWasm({ log: forwardWasmLog, wasmUrl: options.wasmUrl });
|
|
27
34
|
}
|
|
28
35
|
return wasmPromise;
|
|
29
36
|
},
|
|
@@ -55,3 +62,11 @@ export function getDefaultTerminalAppSession(): TerminalAppSession {
|
|
|
55
62
|
}
|
|
56
63
|
return defaultTerminalAppSession;
|
|
57
64
|
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Start loading (and compiling) the terminal WASM core for a session ahead of
|
|
68
|
+
* the first pane. Idempotent — the session caches its in-flight load.
|
|
69
|
+
*/
|
|
70
|
+
export function prewarmTerminalCore(session?: TerminalAppSession): Promise<TerminalWasm> {
|
|
71
|
+
return (session ?? getDefaultTerminalAppSession()).getWasm();
|
|
72
|
+
}
|
|
@@ -147,6 +147,12 @@ export type TerminalUrlFontSource = {
|
|
|
147
147
|
url: string;
|
|
148
148
|
/** Human-readable label for debug/log output. */
|
|
149
149
|
label?: string;
|
|
150
|
+
/**
|
|
151
|
+
* Load after the first frame instead of blocking startup. Use for large,
|
|
152
|
+
* rarely-needed fallback faces (symbol/emoji/CJK); the primary chain must
|
|
153
|
+
* stay eager.
|
|
154
|
+
*/
|
|
155
|
+
defer?: boolean;
|
|
150
156
|
};
|
|
151
157
|
|
|
152
158
|
/** Font source loaded from an in-memory buffer. */
|
|
@@ -156,6 +162,8 @@ export type TerminalBufferFontSource = {
|
|
|
156
162
|
data: TerminalFontBufferData;
|
|
157
163
|
/** Human-readable label for debug/log output. */
|
|
158
164
|
label?: string;
|
|
165
|
+
/** Load after the first frame instead of blocking startup. */
|
|
166
|
+
defer?: boolean;
|
|
159
167
|
};
|
|
160
168
|
|
|
161
169
|
/** Font source resolved from locally installed fonts via the Local Font Access API. */
|
|
@@ -167,6 +175,8 @@ export type TerminalLocalFontSource = {
|
|
|
167
175
|
label?: string;
|
|
168
176
|
/** If true, font loading fails when no local match is found. */
|
|
169
177
|
required?: boolean;
|
|
178
|
+
/** Load after the first frame instead of blocking startup. */
|
|
179
|
+
defer?: boolean;
|
|
170
180
|
};
|
|
171
181
|
|
|
172
182
|
/**
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Auto-generated by build:wasm script. Do not edit by hand.
|
|
2
|
+
// Build provenance only — intentionally free of the binary so importing
|
|
3
|
+
// it does not pull the WASM into an application bundle.
|
|
4
|
+
// Source: wasm/zig-out/bin/terminal.wasm
|
|
5
|
+
export const GHOSTTY_VERSION: string = "1.3.2-dev";
|
|
6
|
+
export const GHOSTTY_COMMIT: string = "e1d31deaaed21aa9225afca78d778fb373c95852";
|
|
7
|
+
export const GHOSTTY_PATCH_SHA256: string = "252172b9c6868bccc0277ab5141e8725fe76a9b619788eccdc6e227686f80da0";
|
|
8
|
+
export const ZIG_VERSION: string = "0.15.2";
|
|
9
|
+
export const TERMINAL_WASM_OPTIMIZE: string = "ReleaseSafe";
|
|
10
|
+
export const TERMINAL_WASM_SIZE: number = 1042257;
|
|
11
|
+
export const TERMINAL_WASM_SHA256: string = "cb2873ac405e163941beb090d68c4acf990957fda51e42d72a1218baa67496de";
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { WASM_BASE64 } from "../embedded";
|
|
1
|
+
import { TERMINAL_WASM_URL } from "../wasm-asset";
|
|
3
2
|
import { resolveWasmAbi } from "./abi";
|
|
4
3
|
import { readKittyPlacements } from "./kitty";
|
|
5
4
|
import { readRenderState } from "./render-state";
|
|
@@ -45,9 +44,9 @@ export class TerminalWasm {
|
|
|
45
44
|
this.renderViewCaches = new Map();
|
|
46
45
|
}
|
|
47
46
|
|
|
48
|
-
/** Load and instantiate the
|
|
47
|
+
/** Load and instantiate the terminal WASM module. */
|
|
49
48
|
static async load(options: TerminalWasmOptions = {}): Promise<TerminalWasm> {
|
|
50
|
-
const bytes =
|
|
49
|
+
const bytes = await loadWasmBytes(options.wasmUrl);
|
|
51
50
|
let memory: WebAssembly.Memory | null = null;
|
|
52
51
|
const log = options.log;
|
|
53
52
|
|
|
@@ -282,7 +281,25 @@ export class TerminalWasm {
|
|
|
282
281
|
}
|
|
283
282
|
}
|
|
284
283
|
|
|
285
|
-
/** Load and instantiate the
|
|
284
|
+
/** Load and instantiate the terminal WASM module (convenience function). */
|
|
286
285
|
export async function loadTerminalWasm(options: TerminalWasmOptions = {}): Promise<TerminalWasm> {
|
|
287
286
|
return TerminalWasm.load(options);
|
|
288
287
|
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Fetch the WASM binary from its asset URL. The binary is a separate asset
|
|
291
|
+
* rather than an embedded base64 string so browsers can cache and compile it
|
|
292
|
+
* independently, and the JS bundle stays out of the multi-megabyte range.
|
|
293
|
+
*/
|
|
294
|
+
async function loadWasmBytes(wasmUrl?: string): Promise<BufferSource> {
|
|
295
|
+
const url = wasmUrl ?? TERMINAL_WASM_URL;
|
|
296
|
+
const response = await fetch(url);
|
|
297
|
+
if (!response.ok) {
|
|
298
|
+
throw new Error(`failed to load terminal WASM from ${url}: HTTP ${response.status}`);
|
|
299
|
+
}
|
|
300
|
+
const buffer = await response.arrayBuffer();
|
|
301
|
+
if (!buffer.byteLength) {
|
|
302
|
+
throw new Error(`terminal WASM asset is empty: ${url}`);
|
|
303
|
+
}
|
|
304
|
+
return buffer;
|
|
305
|
+
}
|
|
@@ -176,6 +176,11 @@ export type TerminalWasmExports = WebAssembly.Exports & {
|
|
|
176
176
|
/** Construction options for WASM runtime. */
|
|
177
177
|
export type TerminalWasmOptions = {
|
|
178
178
|
log?: (message: string) => void;
|
|
179
|
+
/**
|
|
180
|
+
* URL of the `terminal.wasm` asset. Defaults to the copy shipped in the
|
|
181
|
+
* package's `assets` directory (resolved by the bundler).
|
|
182
|
+
*/
|
|
183
|
+
wasmUrl?: string;
|
|
179
184
|
};
|
|
180
185
|
|
|
181
186
|
export type RenderPtrs = {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL of the standalone terminal WASM asset, emitted next to the bundle by the
|
|
3
|
+
* consuming bundler (`new URL(..., import.meta.url)` is understood by Vite and
|
|
4
|
+
* most other modern bundlers). Kept in its own module so applications can pass
|
|
5
|
+
* an explicit `wasmUrl` when they copy the asset themselves.
|
|
6
|
+
*/
|
|
7
|
+
export const TERMINAL_WASM_URL = new URL(
|
|
8
|
+
"../../../assets/terminal.wasm",
|
|
9
|
+
import.meta.url,
|
|
10
|
+
).href;
|