@floegence/floeterm-terminal-web 0.5.21 → 0.5.23
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 +17 -2
- package/dist/core/PagedTerminalOutputCoordinator.d.ts +6 -0
- package/dist/core/PagedTerminalOutputCoordinator.d.ts.map +1 -1
- package/dist/core/PagedTerminalOutputCoordinator.js +30 -0
- package/dist/core/PagedTerminalOutputCoordinator.js.map +1 -1
- package/dist/core/TerminalCore.d.ts +4 -3
- package/dist/core/TerminalCore.d.ts.map +1 -1
- package/dist/core/TerminalCore.js +49 -95
- package/dist/core/TerminalCore.js.map +1 -1
- package/dist/core/TerminalInputBridge.js +1 -1
- package/dist/core/TerminalInputBridge.js.map +1 -1
- package/dist/entries/history.d.ts +4 -0
- package/dist/entries/history.d.ts.map +1 -0
- package/dist/entries/history.js +2 -0
- package/dist/entries/history.js.map +1 -0
- package/dist/entries/preload.d.ts +3 -0
- package/dist/entries/preload.d.ts.map +1 -0
- package/dist/entries/preload.js +2 -0
- package/dist/entries/preload.js.map +1 -0
- package/dist/entries/sessions.d.ts +4 -0
- package/dist/entries/sessions.d.ts.map +1 -0
- package/dist/entries/sessions.js +2 -0
- package/dist/entries/sessions.js.map +1 -0
- package/dist/fabric/BeamtermFabricRenderer.d.ts +3 -3
- package/dist/fabric/BeamtermFabricRenderer.d.ts.map +1 -1
- package/dist/fabric/BeamtermFabricRenderer.js +2 -13
- package/dist/fabric/BeamtermFabricRenderer.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/BeamtermResourceLoader.d.ts +3 -0
- package/dist/internal/BeamtermResourceLoader.d.ts.map +1 -0
- package/dist/internal/BeamtermResourceLoader.js +14 -0
- package/dist/internal/BeamtermResourceLoader.js.map +1 -0
- package/dist/internal/TerminalResourceLoader.d.ts +7 -0
- package/dist/internal/TerminalResourceLoader.d.ts.map +1 -0
- package/dist/internal/TerminalResourceLoader.js +61 -0
- package/dist/internal/TerminalResourceLoader.js.map +1 -0
- package/dist/sessions/TerminalSessionsCoordinator.d.ts.map +1 -1
- package/dist/sessions/TerminalSessionsCoordinator.js +5 -1
- package/dist/sessions/TerminalSessionsCoordinator.js.map +1 -1
- package/package.json +20 -2
|
@@ -4,7 +4,7 @@ import { cursorToFabricCursor, mapGhosttyRowToFabricCells, renderReasonFromForce
|
|
|
4
4
|
import { terminalFabricCoordinator } from '../fabric/TerminalFabricCoordinator.js';
|
|
5
5
|
import { TerminalState, } from '../types.js';
|
|
6
6
|
import { terminalInitializationScheduler, } from '../internal/TerminalInitializationScheduler.js';
|
|
7
|
-
import {
|
|
7
|
+
import { getGhosttyFitAddonConstructor, getGhosttyTerminalConstructor, loadGhosttyModules, waitWithAbort, } from '../internal/TerminalResourceLoader.js';
|
|
8
8
|
import { resolveTerminalInputElement, TerminalInputBridge } from './TerminalInputBridge.js';
|
|
9
9
|
import { terminalRenderScheduler } from './TerminalRenderScheduler.js';
|
|
10
10
|
import { scheduleUiTurn } from '../internal/scheduleUiTurn.js';
|
|
@@ -113,64 +113,7 @@ const getPerfProbe = () => {
|
|
|
113
113
|
}
|
|
114
114
|
return window.__floetermPerfProbe;
|
|
115
115
|
};
|
|
116
|
-
|
|
117
|
-
let TerminalCtor = null;
|
|
118
|
-
let FitAddonCtor = null;
|
|
119
|
-
let ghosttyResourcesPromise = null;
|
|
120
|
-
const abortError = () => {
|
|
121
|
-
if (typeof DOMException !== 'undefined')
|
|
122
|
-
return new DOMException('Operation aborted', 'AbortError');
|
|
123
|
-
const error = new Error('Operation aborted');
|
|
124
|
-
error.name = 'AbortError';
|
|
125
|
-
return error;
|
|
126
|
-
};
|
|
127
|
-
const waitWithAbort = (promise, signal) => {
|
|
128
|
-
if (!signal)
|
|
129
|
-
return promise;
|
|
130
|
-
if (signal.aborted)
|
|
131
|
-
return Promise.reject(abortError());
|
|
132
|
-
return new Promise((resolve, reject) => {
|
|
133
|
-
const onAbort = () => reject(abortError());
|
|
134
|
-
signal.addEventListener('abort', onAbort, { once: true });
|
|
135
|
-
promise.then(value => {
|
|
136
|
-
signal.removeEventListener('abort', onAbort);
|
|
137
|
-
resolve(value);
|
|
138
|
-
}, error => {
|
|
139
|
-
signal.removeEventListener('abort', onAbort);
|
|
140
|
-
reject(error);
|
|
141
|
-
});
|
|
142
|
-
});
|
|
143
|
-
};
|
|
144
|
-
const loadGhosttyModules = async (logger, signal) => {
|
|
145
|
-
if (typeof window === 'undefined') {
|
|
146
|
-
throw new Error('ghostty-web 只能在浏览器环境中加载');
|
|
147
|
-
}
|
|
148
|
-
if (TerminalCtor && FitAddonCtor) {
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
if (!ghosttyResourcesPromise) {
|
|
152
|
-
logger.debug('[TerminalCore] Initializing ghostty-web WASM');
|
|
153
|
-
ghosttyResourcesPromise = (async () => {
|
|
154
|
-
const { Terminal, FitAddon, init } = await import('ghostty-web');
|
|
155
|
-
await init();
|
|
156
|
-
TerminalCtor = Terminal;
|
|
157
|
-
FitAddonCtor = FitAddon;
|
|
158
|
-
})().catch((error) => {
|
|
159
|
-
TerminalCtor = null;
|
|
160
|
-
FitAddonCtor = null;
|
|
161
|
-
ghosttyResourcesPromise = null;
|
|
162
|
-
throw error;
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
await waitWithAbort(ghosttyResourcesPromise, signal);
|
|
166
|
-
};
|
|
167
|
-
export const preloadTerminalResources = async (options = {}) => {
|
|
168
|
-
const resources = Promise.all([
|
|
169
|
-
loadGhosttyModules(options.logger ?? noopLogger),
|
|
170
|
-
loadBeamtermModule(),
|
|
171
|
-
]).then(() => undefined);
|
|
172
|
-
await waitWithAbort(resources, options.signal);
|
|
173
|
-
};
|
|
116
|
+
export { preloadTerminalResources } from '../internal/TerminalResourceLoader.js';
|
|
174
117
|
const mapThemeToGhostty = (theme) => {
|
|
175
118
|
if (!theme) {
|
|
176
119
|
return {};
|
|
@@ -404,6 +347,7 @@ export class TerminalCore {
|
|
|
404
347
|
this.fabricAttachSeq = 0;
|
|
405
348
|
this.cancelFabricAttachSchedule = null;
|
|
406
349
|
this.cancelInputRefocusSchedule = null;
|
|
350
|
+
this.inputElement = null;
|
|
407
351
|
this.viewportHost = null;
|
|
408
352
|
this.renderHost = null;
|
|
409
353
|
this.resizeObserver = null;
|
|
@@ -588,12 +532,15 @@ export class TerminalCore {
|
|
|
588
532
|
this.fabricView = null;
|
|
589
533
|
this.terminal?.dispose();
|
|
590
534
|
this.terminal = null;
|
|
535
|
+
this.inputElement?.remove();
|
|
536
|
+
this.inputElement = null;
|
|
591
537
|
this.fitAddon = null;
|
|
592
538
|
this.viewportHost?.remove();
|
|
593
539
|
this.viewportHost = null;
|
|
594
540
|
this.renderHost = null;
|
|
595
541
|
}
|
|
596
542
|
async createTerminalInstance(signal) {
|
|
543
|
+
const TerminalCtor = getGhosttyTerminalConstructor();
|
|
597
544
|
if (!TerminalCtor) {
|
|
598
545
|
throw new Error('ghostty-web module not loaded');
|
|
599
546
|
}
|
|
@@ -644,6 +591,7 @@ export class TerminalCore {
|
|
|
644
591
|
if (!this.terminal) {
|
|
645
592
|
throw new Error('Terminal instance not created');
|
|
646
593
|
}
|
|
594
|
+
const FitAddonCtor = getGhosttyFitAddonConstructor();
|
|
647
595
|
if (!FitAddonCtor) {
|
|
648
596
|
throw new Error('Required ghostty-web addons not loaded');
|
|
649
597
|
}
|
|
@@ -661,6 +609,7 @@ export class TerminalCore {
|
|
|
661
609
|
this.applyPresentationScaleStyles();
|
|
662
610
|
this.installDemandRenderPatchBeforeOpen();
|
|
663
611
|
this.terminal.open(this.renderHost ?? this.container);
|
|
612
|
+
this.mountInputElement();
|
|
664
613
|
this.stopGhosttyRenderLoop();
|
|
665
614
|
this.patchDemandRenderTriggersAfterOpen();
|
|
666
615
|
this.patchSelectionManagerClipboardBehavior();
|
|
@@ -669,6 +618,18 @@ export class TerminalCore {
|
|
|
669
618
|
this.applyRegisteredLinkProviders();
|
|
670
619
|
void this.refreshFontMetricsAfterLoad('open');
|
|
671
620
|
}
|
|
621
|
+
mountInputElement() {
|
|
622
|
+
const input = resolveTerminalInputElement(this.renderHost ?? this.container);
|
|
623
|
+
if (!input) {
|
|
624
|
+
throw new Error('ghostty-web terminal input element was not created');
|
|
625
|
+
}
|
|
626
|
+
const body = this.container.ownerDocument.body;
|
|
627
|
+
if (!body) {
|
|
628
|
+
throw new Error('Terminal input requires a document body');
|
|
629
|
+
}
|
|
630
|
+
body.appendChild(input);
|
|
631
|
+
this.inputElement = input;
|
|
632
|
+
}
|
|
672
633
|
installFitAddonGeometryPatch() {
|
|
673
634
|
const fitAddon = this.fitAddon;
|
|
674
635
|
if (!fitAddon || fitAddon.__floetermGeometryPatchApplied) {
|
|
@@ -791,7 +752,7 @@ export class TerminalCore {
|
|
|
791
752
|
this.syncImeInputAnchor();
|
|
792
753
|
}
|
|
793
754
|
syncImeInputAnchor() {
|
|
794
|
-
const input =
|
|
755
|
+
const input = this.inputElement;
|
|
795
756
|
if (!input || this.shouldPreserveTransientInputPlacement(input)) {
|
|
796
757
|
return;
|
|
797
758
|
}
|
|
@@ -801,17 +762,34 @@ export class TerminalCore {
|
|
|
801
762
|
return;
|
|
802
763
|
}
|
|
803
764
|
const canvasRect = geometry.canvas.getBoundingClientRect();
|
|
804
|
-
const
|
|
805
|
-
const
|
|
806
|
-
if (!Number.isFinite(
|
|
765
|
+
const canvasClientWidth = Number(geometry.canvas.clientWidth);
|
|
766
|
+
const canvasClientHeight = Number(geometry.canvas.clientHeight);
|
|
767
|
+
if (!Number.isFinite(canvasClientWidth)
|
|
768
|
+
|| !Number.isFinite(canvasClientHeight)
|
|
769
|
+
|| canvasClientWidth <= 0
|
|
770
|
+
|| canvasClientHeight <= 0) {
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
const visibleScaleX = Number(canvasRect.width) / canvasClientWidth;
|
|
774
|
+
const visibleScaleY = Number(canvasRect.height) / canvasClientHeight;
|
|
775
|
+
const cellWidth = geometry.cellWidth * visibleScaleX;
|
|
776
|
+
const cellHeight = geometry.cellHeight * visibleScaleY;
|
|
777
|
+
const left = Number(canvasRect.left) + cursor.x * cellWidth;
|
|
778
|
+
const top = Number(canvasRect.top) + cursor.y * cellHeight;
|
|
779
|
+
if (!Number.isFinite(left)
|
|
780
|
+
|| !Number.isFinite(top)
|
|
781
|
+
|| !Number.isFinite(cellWidth)
|
|
782
|
+
|| !Number.isFinite(cellHeight)
|
|
783
|
+
|| cellWidth <= 0
|
|
784
|
+
|| cellHeight <= 0) {
|
|
807
785
|
return;
|
|
808
786
|
}
|
|
809
787
|
input.style.position = 'fixed';
|
|
810
788
|
input.style.left = formatCssPixelValue(left);
|
|
811
789
|
input.style.top = formatCssPixelValue(top);
|
|
812
|
-
input.style.width = formatCssPixelValue(
|
|
813
|
-
input.style.height = formatCssPixelValue(
|
|
814
|
-
input.style.lineHeight = formatCssPixelValue(
|
|
790
|
+
input.style.width = formatCssPixelValue(cellWidth);
|
|
791
|
+
input.style.height = formatCssPixelValue(cellHeight);
|
|
792
|
+
input.style.lineHeight = formatCssPixelValue(cellHeight);
|
|
815
793
|
input.style.padding = '0';
|
|
816
794
|
input.style.border = 'none';
|
|
817
795
|
input.style.margin = '0';
|
|
@@ -822,35 +800,10 @@ export class TerminalCore {
|
|
|
822
800
|
input.style.clipPath = 'none';
|
|
823
801
|
input.style.pointerEvents = 'none';
|
|
824
802
|
input.style.zIndex = '0';
|
|
825
|
-
this.calibrateImeInputAnchorRect(input, left, top);
|
|
826
803
|
}
|
|
827
804
|
shouldPreserveTransientInputPlacement(input) {
|
|
828
805
|
return input.style.pointerEvents === 'auto' || input.style.zIndex === '1000';
|
|
829
806
|
}
|
|
830
|
-
calibrateImeInputAnchorRect(input, targetLeft, targetTop) {
|
|
831
|
-
const rect = input.getBoundingClientRect();
|
|
832
|
-
if (rect.width <= 0
|
|
833
|
-
|| rect.height <= 0
|
|
834
|
-
|| !Number.isFinite(rect.left)
|
|
835
|
-
|| !Number.isFinite(rect.top)) {
|
|
836
|
-
return;
|
|
837
|
-
}
|
|
838
|
-
const deltaLeft = targetLeft - Number(rect.left);
|
|
839
|
-
const deltaTop = targetTop - Number(rect.top);
|
|
840
|
-
if (Math.abs(deltaLeft) < 0.5 && Math.abs(deltaTop) < 0.5) {
|
|
841
|
-
return;
|
|
842
|
-
}
|
|
843
|
-
const currentLeft = Number.parseFloat(input.style.left);
|
|
844
|
-
const currentTop = Number.parseFloat(input.style.top);
|
|
845
|
-
const nextLeft = (Number.isFinite(currentLeft) ? currentLeft : targetLeft) + deltaLeft;
|
|
846
|
-
const nextTop = (Number.isFinite(currentTop) ? currentTop : targetTop) + deltaTop;
|
|
847
|
-
if (Number.isFinite(nextLeft)) {
|
|
848
|
-
input.style.left = formatCssPixelValue(nextLeft);
|
|
849
|
-
}
|
|
850
|
-
if (Number.isFinite(nextTop)) {
|
|
851
|
-
input.style.top = formatCssPixelValue(nextTop);
|
|
852
|
-
}
|
|
853
|
-
}
|
|
854
807
|
resolveImeInputAnchorGeometry() {
|
|
855
808
|
const terminalAny = this.terminal;
|
|
856
809
|
const renderer = terminalAny?.renderer ?? null;
|
|
@@ -873,9 +826,8 @@ export class TerminalCore {
|
|
|
873
826
|
const metrics = renderer?.getMetrics?.() ?? renderer?.metrics ?? {};
|
|
874
827
|
const cols = Math.floor(Number(terminalAny?.cols));
|
|
875
828
|
const rows = Math.floor(Number(terminalAny?.rows));
|
|
876
|
-
const
|
|
877
|
-
const
|
|
878
|
-
const fallbackCellHeight = rows > 0 ? Number(canvasRect.height) / rows : Number.NaN;
|
|
829
|
+
const fallbackCellWidth = cols > 0 ? Number(canvas.clientWidth) / cols : Number.NaN;
|
|
830
|
+
const fallbackCellHeight = rows > 0 ? Number(canvas.clientHeight) / rows : Number.NaN;
|
|
879
831
|
const cellWidth = Number(metrics.width ?? renderer?.charWidth ?? fallbackCellWidth);
|
|
880
832
|
const cellHeight = Number(metrics.height ?? renderer?.charHeight ?? fallbackCellHeight);
|
|
881
833
|
if (!this.isUsableAnchorGeometry(cellWidth, cellHeight, cols, rows)) {
|
|
@@ -1309,7 +1261,7 @@ export class TerminalCore {
|
|
|
1309
1261
|
}
|
|
1310
1262
|
setupInputBridge() {
|
|
1311
1263
|
this.disposeInputBridge();
|
|
1312
|
-
const input =
|
|
1264
|
+
const input = this.inputElement;
|
|
1313
1265
|
if (!input) {
|
|
1314
1266
|
this.logger.debug('[TerminalCore] No terminal input host found for input bridge');
|
|
1315
1267
|
return;
|
|
@@ -2727,6 +2679,8 @@ export class TerminalCore {
|
|
|
2727
2679
|
this.fabricView = null;
|
|
2728
2680
|
this.terminal?.dispose();
|
|
2729
2681
|
this.terminal = null;
|
|
2682
|
+
this.inputElement?.remove();
|
|
2683
|
+
this.inputElement = null;
|
|
2730
2684
|
this.viewportHost = null;
|
|
2731
2685
|
this.renderHost = null;
|
|
2732
2686
|
this.registeredLinkProviders.clear();
|