@floegence/floeterm-terminal-web 0.4.21 → 0.4.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 +13 -0
- package/dist/core/TerminalCore.d.ts +9 -2
- package/dist/core/TerminalCore.d.ts.map +1 -1
- package/dist/core/TerminalCore.js +150 -9
- package/dist/core/TerminalCore.js.map +1 -1
- package/dist/hooks/useTerminalInstance.d.ts.map +1 -1
- package/dist/hooks/useTerminalInstance.js +150 -105
- package/dist/hooks/useTerminalInstance.js.map +1 -1
- package/dist/internal/SequenceBuffer.d.ts +1 -0
- package/dist/internal/SequenceBuffer.d.ts.map +1 -1
- package/dist/internal/SequenceBuffer.js +18 -0
- package/dist/internal/SequenceBuffer.js.map +1 -1
- package/dist/types.d.ts +17 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +13 -6
- package/dist/utils/config.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -47,6 +47,19 @@ const core = new TerminalCore(container, {
|
|
|
47
47
|
});
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
+
Passive mirrors of a remote PTY can render with the session owner's current dimensions instead of fitting their own
|
|
51
|
+
container:
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
const core = new TerminalCore(container, {
|
|
55
|
+
fixedDimensions: { cols: 100, rows: 30 },
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Later, when this surface becomes the active geometry owner:
|
|
59
|
+
core.setFixedDimensions(null);
|
|
60
|
+
core.forceResize();
|
|
61
|
+
```
|
|
62
|
+
|
|
50
63
|
## Clipboard behavior
|
|
51
64
|
By default, upstream mouse selection keeps the `ghostty-web` behavior and copies immediately on selection.
|
|
52
65
|
Consumers that want explicit copy commands only can disable that side effect:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TerminalState, type Logger, type TerminalConfig, type TerminalCopySelectionResult, type TerminalCopySelectionSource, type TerminalEventHandlers, type TerminalLinkProvider } from '../types';
|
|
1
|
+
import { TerminalState, type Logger, type TerminalConfig, type TerminalCopySelectionResult, type TerminalCopySelectionSource, type TerminalDimensions, type TerminalEventHandlers, type TerminalLinkProvider } from '../types';
|
|
2
2
|
export declare class TerminalCore {
|
|
3
3
|
private container;
|
|
4
4
|
private config;
|
|
@@ -11,6 +11,7 @@ export declare class TerminalCore {
|
|
|
11
11
|
private renderHost;
|
|
12
12
|
private resizeObserver;
|
|
13
13
|
private resizeDebounceTimer;
|
|
14
|
+
private resizeRaf;
|
|
14
15
|
private focusResizeRaf;
|
|
15
16
|
private presentationScaleRaf;
|
|
16
17
|
private state;
|
|
@@ -23,6 +24,7 @@ export declare class TerminalCore {
|
|
|
23
24
|
private responsive;
|
|
24
25
|
private logicalFontSize;
|
|
25
26
|
private presentationScale;
|
|
27
|
+
private fixedDimensions;
|
|
26
28
|
private pendingPresentationScale;
|
|
27
29
|
private suppressResizeNotifications;
|
|
28
30
|
private clearResizeSuppressionRaf;
|
|
@@ -51,18 +53,22 @@ export declare class TerminalCore {
|
|
|
51
53
|
private openTerminal;
|
|
52
54
|
private installDemandRenderPatchBeforeOpen;
|
|
53
55
|
private patchDemandRenderTriggersAfterOpen;
|
|
56
|
+
private patchRendererCursorBlinkForDemandRendering;
|
|
57
|
+
private keepDemandCursorVisible;
|
|
54
58
|
private installRendererRowCachePatch;
|
|
55
59
|
private ensurePresentationHosts;
|
|
56
60
|
private applyPresentationScaleStyles;
|
|
57
61
|
private patchSelectionManagerClipboardBehavior;
|
|
62
|
+
private patchSelectionManagerRenderingBehavior;
|
|
58
63
|
private setupInputBridge;
|
|
59
64
|
private waitForDOMAndFonts;
|
|
60
65
|
private ensureContainerReady;
|
|
61
66
|
private setupEventListeners;
|
|
62
67
|
private setupResponsiveListeners;
|
|
63
68
|
private startSizeWatching;
|
|
64
|
-
private
|
|
69
|
+
private scheduleObservedResize;
|
|
65
70
|
private performResize;
|
|
71
|
+
private applyFixedDimensions;
|
|
66
72
|
private scheduleFocusResize;
|
|
67
73
|
private emitResize;
|
|
68
74
|
private isContainerFocused;
|
|
@@ -127,6 +133,7 @@ export declare class TerminalCore {
|
|
|
127
133
|
focus(): void;
|
|
128
134
|
setConnected(isConnected: boolean): void;
|
|
129
135
|
forceResize(): void;
|
|
136
|
+
setFixedDimensions(dimensions: TerminalDimensions | null): void;
|
|
130
137
|
setTheme(theme: Record<string, string>): void;
|
|
131
138
|
setFontSize(size: number): void;
|
|
132
139
|
setPresentationScale(scale: number): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TerminalCore.d.ts","sourceRoot":"","sources":["../../src/core/TerminalCore.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,aAAa,EACb,KAAK,MAAM,EAEX,KAAK,cAAc,EACnB,KAAK,2BAA2B,EAChC,KAAK,2BAA2B,EAChC,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EAG1B,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"TerminalCore.d.ts","sourceRoot":"","sources":["../../src/core/TerminalCore.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,6 +8,14 @@ const PRESENTATION_SCALE_EPSILON = 0.0001;
|
|
|
8
8
|
const TERMINAL_SEARCH_MATCH_BACKGROUND = 'rgba(255, 234, 0, 0.38)';
|
|
9
9
|
const TERMINAL_SEARCH_ACTIVE_BACKGROUND = 'rgba(255, 234, 0, 0.72)';
|
|
10
10
|
const TERMINAL_SEARCH_ACTIVE_FOREGROUND = '#dc2626';
|
|
11
|
+
const TERMINAL_SELECTION_BACKGROUND = '#f5e6b3';
|
|
12
|
+
const TERMINAL_SELECTION_FOREGROUND = '#1f2328';
|
|
13
|
+
const getPerfProbe = () => {
|
|
14
|
+
if (typeof window === 'undefined') {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
return window.__floetermPerfProbe;
|
|
18
|
+
};
|
|
11
19
|
// Dynamic imports avoid SSR issues and keep the bundle flexible.
|
|
12
20
|
let TerminalCtor = null;
|
|
13
21
|
let FitAddonCtor = null;
|
|
@@ -96,6 +104,21 @@ function canSkipCachedRowRender(renderer, row) {
|
|
|
96
104
|
function samePresentationScale(left, right) {
|
|
97
105
|
return Math.abs(left - right) <= PRESENTATION_SCALE_EPSILON;
|
|
98
106
|
}
|
|
107
|
+
function normalizeTerminalDimensions(value) {
|
|
108
|
+
const raw = (typeof value === 'object' && value) ? value : null;
|
|
109
|
+
if (!raw) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
const cols = Math.floor(Number(raw.cols));
|
|
113
|
+
const rows = Math.floor(Number(raw.rows));
|
|
114
|
+
if (!Number.isFinite(cols) || !Number.isFinite(rows) || cols <= 0 || rows <= 0) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
return { cols, rows };
|
|
118
|
+
}
|
|
119
|
+
function sameTerminalDimensions(left, right) {
|
|
120
|
+
return Boolean(left && right && left.cols === right.cols && left.rows === right.rows);
|
|
121
|
+
}
|
|
99
122
|
function hasTransientRendererState(renderer) {
|
|
100
123
|
return Boolean(renderer.currentSelectionCoords
|
|
101
124
|
|| (renderer.hoveredHyperlinkId ?? 0) > 0
|
|
@@ -115,6 +138,7 @@ export class TerminalCore {
|
|
|
115
138
|
this.renderHost = null;
|
|
116
139
|
this.resizeObserver = null;
|
|
117
140
|
this.resizeDebounceTimer = null;
|
|
141
|
+
this.resizeRaf = null;
|
|
118
142
|
this.focusResizeRaf = null;
|
|
119
143
|
this.presentationScaleRaf = null;
|
|
120
144
|
this.state = TerminalState.IDLE;
|
|
@@ -123,6 +147,7 @@ export class TerminalCore {
|
|
|
123
147
|
this.replayingHistoryTimer = null;
|
|
124
148
|
this.logicalFontSize = 12;
|
|
125
149
|
this.presentationScale = 1;
|
|
150
|
+
this.fixedDimensions = null;
|
|
126
151
|
this.pendingPresentationScale = null;
|
|
127
152
|
this.suppressResizeNotifications = false;
|
|
128
153
|
this.clearResizeSuppressionRaf = null;
|
|
@@ -151,6 +176,7 @@ export class TerminalCore {
|
|
|
151
176
|
this.responsive = TerminalCore.normalizeResponsiveConfig(config?.responsive);
|
|
152
177
|
this.logicalFontSize = TerminalCore.normalizeFontSize(config?.fontSize);
|
|
153
178
|
this.presentationScale = TerminalCore.normalizePresentationScale(config?.presentationScale);
|
|
179
|
+
this.fixedDimensions = normalizeTerminalDimensions(config?.fixedDimensions);
|
|
154
180
|
}
|
|
155
181
|
// initialize creates the ghostty-web terminal instance and binds addons.
|
|
156
182
|
async initialize() {
|
|
@@ -188,11 +214,12 @@ export class TerminalCore {
|
|
|
188
214
|
background: '#1a1a1a',
|
|
189
215
|
foreground: '#ffffff',
|
|
190
216
|
cursor: '#ffffff',
|
|
191
|
-
selectionBackground:
|
|
217
|
+
selectionBackground: TERMINAL_SELECTION_BACKGROUND,
|
|
218
|
+
selectionForeground: TERMINAL_SELECTION_FOREGROUND
|
|
192
219
|
},
|
|
193
220
|
fontSize: 12,
|
|
194
221
|
fontFamily: '\"SF Mono\", Monaco, \"Cascadia Code\", \"Roboto Mono\", Consolas, \"Courier New\", monospace',
|
|
195
|
-
cursorBlink:
|
|
222
|
+
cursorBlink: false,
|
|
196
223
|
scrollback: 1000,
|
|
197
224
|
allowTransparency: false,
|
|
198
225
|
convertEol: true,
|
|
@@ -203,9 +230,10 @@ export class TerminalCore {
|
|
|
203
230
|
const finalConfig = { ...defaultConfig, ...this.config };
|
|
204
231
|
this.logicalFontSize = TerminalCore.normalizeFontSize(finalConfig.fontSize);
|
|
205
232
|
this.presentationScale = TerminalCore.normalizePresentationScale(finalConfig.presentationScale);
|
|
233
|
+
const initialDimensions = this.fixedDimensions;
|
|
206
234
|
this.terminal = new TerminalCtor({
|
|
207
|
-
cols: typeof finalConfig.cols === 'number' ? finalConfig.cols : undefined,
|
|
208
|
-
rows: typeof finalConfig.rows === 'number' ? finalConfig.rows : undefined,
|
|
235
|
+
cols: initialDimensions?.cols ?? (typeof finalConfig.cols === 'number' ? finalConfig.cols : undefined),
|
|
236
|
+
rows: initialDimensions?.rows ?? (typeof finalConfig.rows === 'number' ? finalConfig.rows : undefined),
|
|
209
237
|
cursorBlink: typeof finalConfig.cursorBlink === 'boolean' ? finalConfig.cursorBlink : undefined,
|
|
210
238
|
cursorStyle: typeof finalConfig.cursorStyle === 'string' ? finalConfig.cursorStyle : undefined,
|
|
211
239
|
theme: mapThemeToGhostty(finalConfig.theme),
|
|
@@ -241,6 +269,7 @@ export class TerminalCore {
|
|
|
241
269
|
this.stopGhosttyRenderLoop();
|
|
242
270
|
this.patchDemandRenderTriggersAfterOpen();
|
|
243
271
|
this.patchSelectionManagerClipboardBehavior();
|
|
272
|
+
this.patchSelectionManagerRenderingBehavior();
|
|
244
273
|
this.setupInputBridge();
|
|
245
274
|
this.applyRegisteredLinkProviders();
|
|
246
275
|
}
|
|
@@ -296,6 +325,29 @@ export class TerminalCore {
|
|
|
296
325
|
wrapRendererInvalidation('setFontSize');
|
|
297
326
|
wrapRendererInvalidation('setFontFamily');
|
|
298
327
|
wrapRendererInvalidation('clear');
|
|
328
|
+
this.patchRendererCursorBlinkForDemandRendering(renderer);
|
|
329
|
+
}
|
|
330
|
+
patchRendererCursorBlinkForDemandRendering(renderer) {
|
|
331
|
+
if (!renderer || renderer.__floetermDemandCursorPatched) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
renderer.__floetermDemandCursorPatched = true;
|
|
335
|
+
renderer.cursorVisible = true;
|
|
336
|
+
try {
|
|
337
|
+
renderer.setCursorBlink?.(false);
|
|
338
|
+
}
|
|
339
|
+
catch (error) {
|
|
340
|
+
this.logger.debug('[TerminalCore] Failed to disable cursor blink for demand rendering', { error });
|
|
341
|
+
}
|
|
342
|
+
if (this.terminal) {
|
|
343
|
+
this.terminal.options.cursorBlink = false;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
keepDemandCursorVisible(renderer) {
|
|
347
|
+
if (!renderer) {
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
renderer.cursorVisible = true;
|
|
299
351
|
}
|
|
300
352
|
installRendererRowCachePatch(renderer) {
|
|
301
353
|
if (!renderer || renderer.__floetermRowRenderCachePatched) {
|
|
@@ -382,6 +434,21 @@ export class TerminalCore {
|
|
|
382
434
|
// the selection lifecycle intact for explicit copy commands.
|
|
383
435
|
};
|
|
384
436
|
}
|
|
437
|
+
patchSelectionManagerRenderingBehavior() {
|
|
438
|
+
const selectionManager = (this.terminal
|
|
439
|
+
?.selectionManager) ?? null;
|
|
440
|
+
if (!selectionManager || selectionManager.__floetermDemandRenderPatched) {
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
selectionManager.__floetermDemandRenderPatched = true;
|
|
444
|
+
const originalRequestRender = typeof selectionManager.requestRender === 'function'
|
|
445
|
+
? selectionManager.requestRender.bind(selectionManager)
|
|
446
|
+
: null;
|
|
447
|
+
selectionManager.requestRender = () => {
|
|
448
|
+
originalRequestRender?.();
|
|
449
|
+
this.requestDemandRender(false);
|
|
450
|
+
};
|
|
451
|
+
}
|
|
385
452
|
setupInputBridge() {
|
|
386
453
|
this.disposeInputBridge();
|
|
387
454
|
const input = resolveTerminalInputElement(this.container);
|
|
@@ -526,23 +593,48 @@ export class TerminalCore {
|
|
|
526
593
|
if (!this.container || !this.fitAddon) {
|
|
527
594
|
return;
|
|
528
595
|
}
|
|
596
|
+
if (typeof ResizeObserver === 'undefined') {
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
529
599
|
this.resizeObserver = new ResizeObserver(() => {
|
|
530
|
-
this.
|
|
600
|
+
this.scheduleObservedResize();
|
|
531
601
|
});
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
602
|
+
const observed = new Set();
|
|
603
|
+
const observe = (target) => {
|
|
604
|
+
if (!target || observed.has(target)) {
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
observed.add(target);
|
|
608
|
+
this.resizeObserver?.observe(target);
|
|
609
|
+
};
|
|
610
|
+
observe(this.container);
|
|
611
|
+
observe(this.container.parentElement);
|
|
612
|
+
observe(this.viewportHost);
|
|
613
|
+
observe(this.renderHost);
|
|
614
|
+
}
|
|
615
|
+
scheduleObservedResize() {
|
|
616
|
+
if (this.resizeRaf === null) {
|
|
617
|
+
this.resizeRaf = requestAnimationFrame(() => {
|
|
618
|
+
this.resizeRaf = null;
|
|
619
|
+
this.performResize('observer');
|
|
620
|
+
});
|
|
621
|
+
}
|
|
535
622
|
if (this.resizeDebounceTimer) {
|
|
536
623
|
clearTimeout(this.resizeDebounceTimer);
|
|
537
624
|
}
|
|
538
625
|
this.resizeDebounceTimer = setTimeout(() => {
|
|
626
|
+
this.resizeDebounceTimer = null;
|
|
539
627
|
this.performResize('observer');
|
|
540
|
-
},
|
|
628
|
+
}, 80);
|
|
541
629
|
}
|
|
542
630
|
performResize(reason) {
|
|
543
631
|
if (!this.isReady() || !this.fitAddon || !this.terminal) {
|
|
544
632
|
return;
|
|
545
633
|
}
|
|
634
|
+
if (this.fixedDimensions) {
|
|
635
|
+
this.applyFixedDimensions(reason);
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
546
638
|
if (this.container.clientWidth === 0 || this.container.clientHeight === 0) {
|
|
547
639
|
return;
|
|
548
640
|
}
|
|
@@ -567,6 +659,35 @@ export class TerminalCore {
|
|
|
567
659
|
}
|
|
568
660
|
this.emitResize(dims, { source: 'core' });
|
|
569
661
|
}
|
|
662
|
+
applyFixedDimensions(reason) {
|
|
663
|
+
if (!this.terminal || !this.fixedDimensions) {
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
const dims = this.fixedDimensions;
|
|
667
|
+
const startSeq = this.resizeNotifySeq;
|
|
668
|
+
const before = this.lastNotifiedSize;
|
|
669
|
+
const changed = this.terminal.cols !== dims.cols || this.terminal.rows !== dims.rows;
|
|
670
|
+
if (changed) {
|
|
671
|
+
try {
|
|
672
|
+
this.terminal.resize(dims.cols, dims.rows);
|
|
673
|
+
}
|
|
674
|
+
catch (error) {
|
|
675
|
+
this.logger.debug('[TerminalCore] Fixed-dimension resize failed', { error });
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
// Some ghostty-web builds may not emit onResize for programmatic resize().
|
|
679
|
+
if (this.resizeNotifySeq !== startSeq) {
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
if (reason === 'focus' && this.responsive.emitResizeOnFocus) {
|
|
683
|
+
const force = Boolean(before) && before.cols === dims.cols && before.rows === dims.rows;
|
|
684
|
+
this.emitResize(dims, { source: 'core', force });
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
if (changed) {
|
|
688
|
+
this.emitResize(dims, { source: 'core' });
|
|
689
|
+
}
|
|
690
|
+
}
|
|
570
691
|
scheduleFocusResize() {
|
|
571
692
|
if (!this.responsive.fitOnFocus && !this.responsive.emitResizeOnFocus) {
|
|
572
693
|
return;
|
|
@@ -644,6 +765,7 @@ export class TerminalCore {
|
|
|
644
765
|
return;
|
|
645
766
|
}
|
|
646
767
|
try {
|
|
768
|
+
getPerfProbe()?.onTerminalWrite?.(typeof data === 'string' ? data.length : data.byteLength);
|
|
647
769
|
const shouldForce = this.needsFullRenderOnNextWrite;
|
|
648
770
|
if (callback || shouldForce) {
|
|
649
771
|
this.terminal.write(data, () => {
|
|
@@ -1277,6 +1399,18 @@ export class TerminalCore {
|
|
|
1277
1399
|
this.performResize('force');
|
|
1278
1400
|
this.forceFullRender();
|
|
1279
1401
|
}
|
|
1402
|
+
setFixedDimensions(dimensions) {
|
|
1403
|
+
const next = normalizeTerminalDimensions(dimensions);
|
|
1404
|
+
if (sameTerminalDimensions(this.fixedDimensions, next) || (!this.fixedDimensions && !next)) {
|
|
1405
|
+
return;
|
|
1406
|
+
}
|
|
1407
|
+
this.fixedDimensions = next;
|
|
1408
|
+
this.config = { ...this.config, fixedDimensions: next };
|
|
1409
|
+
if (!this.terminal) {
|
|
1410
|
+
return;
|
|
1411
|
+
}
|
|
1412
|
+
this.forceResize();
|
|
1413
|
+
}
|
|
1280
1414
|
setTheme(theme) {
|
|
1281
1415
|
const mapped = mapThemeToGhostty(theme);
|
|
1282
1416
|
// Persist latest theme so a future re-initialization can reuse it.
|
|
@@ -1360,6 +1494,10 @@ export class TerminalCore {
|
|
|
1360
1494
|
cancelAnimationFrame(this.clearResizeSuppressionRaf);
|
|
1361
1495
|
this.clearResizeSuppressionRaf = null;
|
|
1362
1496
|
}
|
|
1497
|
+
if (this.resizeRaf !== null) {
|
|
1498
|
+
cancelAnimationFrame(this.resizeRaf);
|
|
1499
|
+
this.resizeRaf = null;
|
|
1500
|
+
}
|
|
1363
1501
|
this.cancelDemandRender();
|
|
1364
1502
|
this.unbindResponsiveListeners?.();
|
|
1365
1503
|
this.unbindResponsiveListeners = null;
|
|
@@ -1530,12 +1668,15 @@ export class TerminalCore {
|
|
|
1530
1668
|
return;
|
|
1531
1669
|
}
|
|
1532
1670
|
try {
|
|
1671
|
+
const startedAt = performance.now();
|
|
1672
|
+
this.keepDemandCursorVisible(terminalAny.renderer);
|
|
1533
1673
|
terminalAny.renderer.render(terminalAny.wasmTerm, forceAll, terminalAny.viewportY ?? 0, terminalAny, terminalAny.scrollbarOpacity);
|
|
1534
1674
|
const cursor = terminalAny.wasmTerm.getCursor?.();
|
|
1535
1675
|
if (cursor && typeof cursor.y === 'number' && cursor.y !== terminalAny.lastCursorY) {
|
|
1536
1676
|
terminalAny.lastCursorY = cursor.y;
|
|
1537
1677
|
terminalAny.cursorMoveEmitter?.fire?.();
|
|
1538
1678
|
}
|
|
1679
|
+
getPerfProbe()?.onTerminalRender?.(performance.now() - startedAt);
|
|
1539
1680
|
}
|
|
1540
1681
|
catch (error) {
|
|
1541
1682
|
this.logger.debug('[TerminalCore] Force render failed', { error });
|