@cplieger/web-terminal-engine 6.0.4 → 6.1.2
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 +16 -16
- package/jsr.json +1 -1
- package/package.json +2 -2
- package/src/render.ts +17 -18
package/README.md
CHANGED
|
@@ -123,22 +123,22 @@ Two engines share nothing: each holds its own DOM handles, timers, listeners, so
|
|
|
123
123
|
|
|
124
124
|
`engine.dispose()` disposes the five parts in reverse build order: the connection closes its socket with code 1000 (no `onClose` fires), stops its heartbeat, history and reconnect timers, drops every session's queued bytes and detaches your callbacks; the mouse controller detaches its four listeners and cancels its pending motion frame; the renderer cancels its pending frame and blink interval, removes its `visibilitychange` listener, empties `output`, removes its overlays and the `--char-w`, `cursor-blink-off` and `term-reverse-video` marks from `termWrap`, and drops every element reference; the scroll controller removes its `scroll` listener; the mode state resets to `POWER_ON_MODES`. A callback may call `dispose()` on the engine that invoked it; the connection stops after the callback returns. Every method stays callable afterwards and answers an empty value rather than throwing:
|
|
125
125
|
|
|
126
|
-
| Part
|
|
127
|
-
|
|
|
128
|
-
| `renderer`
|
|
129
|
-
| `renderer`
|
|
130
|
-
| `renderer`
|
|
131
|
-
| `renderer`
|
|
132
|
-
| `renderer`
|
|
133
|
-
| `renderer`
|
|
134
|
-
| `renderer`
|
|
135
|
-
| `renderer`
|
|
136
|
-
| `scroll`
|
|
137
|
-
| `scroll`
|
|
138
|
-
| `connection` | `sendBinary()`, `sendEphemeral()`, `requestHistory()`
|
|
139
|
-
| `connection` | `historyBudget()`, `serverEpochOf()`
|
|
140
|
-
| `connection` | `currentSessionId()`
|
|
141
|
-
| `modes`
|
|
126
|
+
| Part | Method | After `dispose()` |
|
|
127
|
+
| --- | --- | --- |
|
|
128
|
+
| `renderer` | `boundStore()` | a new, empty `LineStore` on every call |
|
|
129
|
+
| `renderer` | `getHighestIndex()`, `getReplayBoundary()` | `-1` |
|
|
130
|
+
| `renderer` | `lastBrowseActivityMs()`, `browseCacheSize()`, `pendingRowCount()` | `0` |
|
|
131
|
+
| `renderer` | `replayMaxForResume()` | `5000` |
|
|
132
|
+
| `renderer` | `captureViewMemory()`, `pendingRestoreAbs()` | `null` |
|
|
133
|
+
| `renderer` | `computeSize()`, `gridSize()` | `{ cols: 0, rows: 0 }` |
|
|
134
|
+
| `renderer` | `cellSize()` | `{ width: 0, height: 0 }` |
|
|
135
|
+
| `renderer` | `getCursorPx()` | `{ left: 0, top: 0, cellH: 0 }` |
|
|
136
|
+
| `scroll` | `isUserScrolledUp()` | `false` |
|
|
137
|
+
| `scroll` | `currentScrollTop()` | `0` |
|
|
138
|
+
| `connection` | `sendBinary()`, `sendEphemeral()`, `requestHistory()` | `false` |
|
|
139
|
+
| `connection` | `historyBudget()`, `serverEpochOf()` | `0` |
|
|
140
|
+
| `connection` | `currentSessionId()` | `""` |
|
|
141
|
+
| `modes` | the nine readers, `snapshot()` | the `POWER_ON_MODES` values; `applySnapshot` is a no-op |
|
|
142
142
|
|
|
143
143
|
`createTerminalEngine` and each factory throw when a listener or timer cannot be acquired, and a throw leaves nothing behind: the parts built before it are disposed in reverse order, so there is no partially built engine to dispose.
|
|
144
144
|
|
package/jsr.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cplieger/web-terminal-engine",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.1.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"typecheck": "tsc -p tsconfig.json",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"@vitest/browser-playwright": "5.0.3",
|
|
26
26
|
"@vitest/coverage-v8": "5.0.3",
|
|
27
27
|
"esbuild": "0.28.2",
|
|
28
|
-
"eslint": "10.
|
|
28
|
+
"eslint": "10.12.0",
|
|
29
29
|
"fast-check": "4.10.2",
|
|
30
30
|
"knip": "6.39.0",
|
|
31
31
|
"playwright": "1.63.0",
|
package/src/render.ts
CHANGED
|
@@ -322,7 +322,7 @@ export function createRenderer(opts: RendererOptions): Renderer {
|
|
|
322
322
|
// read-anchor binary searches keep their monotonic-`data-abs` invariant; an
|
|
323
323
|
// INTERIOR marker cannot be data-abs-less the way the top trim marker is. A
|
|
324
324
|
// marker is a PROJECTION of the store's gap geometry, re-derived whenever
|
|
325
|
-
// either edge moves and removed when the gap closes
|
|
325
|
+
// either edge moves and removed when the gap closes.
|
|
326
326
|
const gapMarkerEls = new Map<number, HTMLDivElement>();
|
|
327
327
|
|
|
328
328
|
let requestHistoryFn: ((fromAbs: number, maxLines: number) => boolean) | null =
|
|
@@ -396,7 +396,7 @@ export function createRenderer(opts: RendererOptions): Renderer {
|
|
|
396
396
|
let fullResetThisPass = false;
|
|
397
397
|
// The ED3 base observed since the last flush, or -1; consumed by the pass that
|
|
398
398
|
// applies it. Renderer-local because the renderer calls every path that
|
|
399
|
-
// discards a REGION rather than trimming the cap
|
|
399
|
+
// discards a REGION rather than trimming the cap.
|
|
400
400
|
let discardedBelowPending = -1;
|
|
401
401
|
let discardedBelowThisPass = -1;
|
|
402
402
|
// Whether THIS pass removed rows. Announcing a shrink for a pass that did not
|
|
@@ -558,7 +558,7 @@ export function createRenderer(opts: RendererOptions): Renderer {
|
|
|
558
558
|
scheduleFlush();
|
|
559
559
|
}
|
|
560
560
|
|
|
561
|
-
// --- demand-paged scrollback: the consumer's seams
|
|
561
|
+
// --- demand-paged scrollback: the consumer's seams ---
|
|
562
562
|
|
|
563
563
|
// The viewport index is supplied here rather than by the caller because the
|
|
564
564
|
// renderer is the only layer that knows it, and the store's eviction needs it
|
|
@@ -590,7 +590,7 @@ export function createRenderer(opts: RendererOptions): Renderer {
|
|
|
590
590
|
// uses the viewport to decide which rows survive, and the live transient
|
|
591
591
|
// would evict the rows an armed restore is about to bring back; the restore
|
|
592
592
|
// names the position the user is REGAINING, so it wins, and it also
|
|
593
|
-
// overrides the live follow flag
|
|
593
|
+
// overrides the live follow flag.
|
|
594
594
|
const pending = pendingRestoreAbs();
|
|
595
595
|
const pendingRestoreArmed = pending !== null;
|
|
596
596
|
store.applyResumeAck({
|
|
@@ -892,7 +892,7 @@ export function createRenderer(opts: RendererOptions): Renderer {
|
|
|
892
892
|
if (msg.scrollbackCleared) {
|
|
893
893
|
// ED3. Recorded at the one place that sees the frame before the store
|
|
894
894
|
// consumes it, so restoreReadAnchor can tell a region DISCARD from a cap
|
|
895
|
-
// trim; the two need opposite recoveries
|
|
895
|
+
// trim; the two need opposite recoveries.
|
|
896
896
|
// Max, because several frames can land between flushes.
|
|
897
897
|
discardedBelowPending = Math.max(discardedBelowPending, msg.base);
|
|
898
898
|
}
|
|
@@ -983,7 +983,7 @@ export function createRenderer(opts: RendererOptions): Renderer {
|
|
|
983
983
|
// The ONE row-selection primitive every "where is the reader" question
|
|
984
984
|
// resolves through (the read anchor, the paging trigger, the per-view memory):
|
|
985
985
|
// two definitions would drift exactly during a rebuild, when the answer
|
|
986
|
-
// matters most
|
|
986
|
+
// matters most. Binary search, since
|
|
987
987
|
// the children are in document order with monotonic offsetTop.
|
|
988
988
|
function rowAtViewportTop(): HTMLElement | null {
|
|
989
989
|
if (!output) {
|
|
@@ -1022,7 +1022,7 @@ export function createRenderer(opts: RendererOptions): Renderer {
|
|
|
1022
1022
|
const el = rowAtViewportTop();
|
|
1023
1023
|
if (el === null) {
|
|
1024
1024
|
// Stand down rather than fall back to the tail: a tail proxy is what
|
|
1025
|
-
// turned a large content shrink into a tail-drag
|
|
1025
|
+
// turned a large content shrink into a tail-drag.
|
|
1026
1026
|
return null;
|
|
1027
1027
|
}
|
|
1028
1028
|
return { el, abs: rowAbs(el), screenTop: el.offsetTop - scroll.currentScrollTop() };
|
|
@@ -1076,10 +1076,10 @@ export function createRenderer(opts: RendererOptions): Renderer {
|
|
|
1076
1076
|
// A region DISCARD (ED3), not a cap trim: nothing surviving is guaranteed
|
|
1077
1077
|
// ADJACENT to what the reader saw. An inline TUI that reprints on resize
|
|
1078
1078
|
// brings the same text back at new indices, and holding the survivor at
|
|
1079
|
-
// the old screen position is the "random jump on resize" symptom
|
|
1080
|
-
//
|
|
1081
|
-
//
|
|
1082
|
-
//
|
|
1079
|
+
// the old screen position is the "random jump on resize" symptom. The test
|
|
1080
|
+
// is the ANCHOR's index only: requiring the SURVIVOR above the base too
|
|
1081
|
+
// never held, since the reprint re-delivers lines below the base in the
|
|
1082
|
+
// same frame.
|
|
1083
1083
|
if (discardedBelowThisPass >= 0 && anchor.abs < discardedBelowThisPass) {
|
|
1084
1084
|
return;
|
|
1085
1085
|
}
|
|
@@ -1149,8 +1149,7 @@ export function createRenderer(opts: RendererOptions): Renderer {
|
|
|
1149
1149
|
// The post-flush trigger. The reader's position relative to the store's gaps
|
|
1150
1150
|
// can change without any scroll event — a tail trim moves the frontier up
|
|
1151
1151
|
// under a stationary reader, and a byte-short page leaves a fresh sub-gap
|
|
1152
|
-
// beside them — so the flush is the other place the trigger must run
|
|
1153
|
-
// (docs/paged-scrollback.md §5.4).
|
|
1152
|
+
// beside them — so the flush is the other place the trigger must run.
|
|
1154
1153
|
maybeFetchHistory();
|
|
1155
1154
|
reportUnnamedDrainStall();
|
|
1156
1155
|
}
|
|
@@ -1317,7 +1316,7 @@ export function createRenderer(opts: RendererOptions): Renderer {
|
|
|
1317
1316
|
}
|
|
1318
1317
|
}
|
|
1319
1318
|
|
|
1320
|
-
// --- Per-view scroll memory
|
|
1319
|
+
// --- Per-view scroll memory ---
|
|
1321
1320
|
// A reading position is a LINE, not a pixel offset: a replayed `scrollTop` is
|
|
1322
1321
|
// silently CLAMPED while a rebuild has built only the live window plus one
|
|
1323
1322
|
// frame's budget, and it stops meaning the same line once the content grows in
|
|
@@ -1410,7 +1409,7 @@ export function createRenderer(opts: RendererOptions): Renderer {
|
|
|
1410
1409
|
|
|
1411
1410
|
// The viewport's absolute index for the paging layer's questions about NOW;
|
|
1412
1411
|
// `pendingRestoreAbs` answers the resume transition, which asks which rows
|
|
1413
|
-
// must SURVIVE a switch
|
|
1412
|
+
// must SURVIVE a switch.
|
|
1414
1413
|
function viewportAbs(): number {
|
|
1415
1414
|
if (!store) {
|
|
1416
1415
|
return 0;
|
|
@@ -1443,7 +1442,7 @@ export function createRenderer(opts: RendererOptions): Renderer {
|
|
|
1443
1442
|
function maybeFetchHistory(): void {
|
|
1444
1443
|
// The pending-demand timer fires from a CLOCK, so this alt guard is the
|
|
1445
1444
|
// load-bearing one: without it a vim session would fetch pages nobody can
|
|
1446
|
-
// see, and each denial would re-arm the timer
|
|
1445
|
+
// see, and each denial would re-arm the timer.
|
|
1447
1446
|
if (!store || store.isAlt()) {
|
|
1448
1447
|
return;
|
|
1449
1448
|
}
|
|
@@ -1526,7 +1525,7 @@ export function createRenderer(opts: RendererOptions): Renderer {
|
|
|
1526
1525
|
// places a row before it. Its frontier is deliberately NOT sourced from gap
|
|
1527
1526
|
// geometry: the lower edge is a policy question (what is still worth
|
|
1528
1527
|
// requesting), so an exhausted frontier still renders a marker rather than
|
|
1529
|
-
// vanishing when the gap closes
|
|
1528
|
+
// vanishing when the gap closes.
|
|
1530
1529
|
function updateTrimMarker(): void {
|
|
1531
1530
|
if (!output) {
|
|
1532
1531
|
return;
|
|
@@ -1557,7 +1556,7 @@ export function createRenderer(opts: RendererOptions): Renderer {
|
|
|
1557
1556
|
// Three honest statements about the history above what is held: nothing when
|
|
1558
1557
|
// index 0 is held; "trimmed" when the client evicted the rows itself with no
|
|
1559
1558
|
// paging, or when paging proved nothing below survives; "not loaded" when
|
|
1560
|
-
// paging is declared and neither proof is in hand
|
|
1559
|
+
// paging is declared and neither proof is in hand.
|
|
1561
1560
|
function topMarkerLabel(): string | null {
|
|
1562
1561
|
if (!store) {
|
|
1563
1562
|
return null;
|