@hex1b/web-terminal 0.172.0 → 0.173.0-alpha.1824.1.98d8766
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 +37 -1
- package/dist/index.js +140 -53
- package/dist/index.js.map +2 -2
- package/dist/mouse-input.d.ts +1 -0
- package/dist/mouse-input.d.ts.map +1 -1
- package/dist/protocol.d.ts.map +1 -1
- package/dist/renderer.d.ts +1 -1
- package/dist/renderer.d.ts.map +1 -1
- package/dist/transport-session.d.ts +1 -0
- package/dist/transport-session.d.ts.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/web-terminal.d.ts.map +1 -1
- package/dist/wire-types.d.ts +3 -0
- package/dist/wire-types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -169,6 +169,7 @@ const container = document.getElementById("terminal");
|
|
|
169
169
|
if (!container) throw new Error("Missing terminal container");
|
|
170
170
|
const terminal = await WebTerminal.mount(container, {
|
|
171
171
|
url: "/ws/terminal",
|
|
172
|
+
preserveOnDisconnect: true,
|
|
172
173
|
colorMode: "system", // "light", "dark", or the browser's prefers-color-scheme.
|
|
173
174
|
lightModePalette: { ...defaultLightPalette, background: "#fafafa" },
|
|
174
175
|
darkModePalette: { ...defaultDarkPalette, background: "#202020" },
|
|
@@ -297,7 +298,41 @@ view after transport loss or leave an ended tab/dialog visible. Abort, explicit
|
|
|
297
298
|
disposal, mount timeout, and local initialization/renderer failures do not
|
|
298
299
|
synthesize `onClose`; no callback runs after disposal. Callback exceptions are
|
|
299
300
|
reported to the host, not swallowed or retried, and do not leave mounting pending.
|
|
300
|
-
|
|
301
|
+
Set `preserveOnDisconnect: true` to finish presenting the last accepted complete
|
|
302
|
+
frame before `onClose` and keep it visible until `dispose()`. The retained view
|
|
303
|
+
can repaint when its container or palette changes, but input and producer-backed
|
|
304
|
+
history, selection, and copying are disconnected. Cursor/text blinking stops.
|
|
305
|
+
The default is `false`, preserving the existing renderer-release behavior.
|
|
306
|
+
Explicit disposal or abort still removes the view and releases its GPU resources.
|
|
307
|
+
Local rendering failures cannot guarantee preservation.
|
|
308
|
+
|
|
309
|
+
Preservation retains the received KGP and Sixel textures and placements, including
|
|
310
|
+
PNG/compressed KGP, cropped and relative placements, Unicode placeholders, and
|
|
311
|
+
the current server-driven animation frame. Animation freezes rather than replaying
|
|
312
|
+
locally. Styled/wide/combining text, DEC enlarged rows, the current alternate
|
|
313
|
+
screen, link decorations already presented, selection highlights, viewport text,
|
|
314
|
+
markers, and activity metadata remain available for display and local redraw.
|
|
315
|
+
Link activation and producer-backed navigation/copy operations stop. Only the
|
|
316
|
+
current received history viewport is retained, not a browsable offline scrollback
|
|
317
|
+
archive; historical HWT1 viewports do not currently include graphics.
|
|
318
|
+
|
|
319
|
+
Preservation alone cannot recover bytes the server never sent. On the server,
|
|
320
|
+
await `Hex1bTerminal.RunAsync()` to finish consuming process output, then await
|
|
321
|
+
`Hwt1PresentationAdapter.DrainAsync(cancellationToken)` **while the frame sender
|
|
322
|
+
and client-message receiver remain running**. Only after the drain completes
|
|
323
|
+
should the host cancel those loops, close the transport, and dispose the
|
|
324
|
+
producer. The drain requests a fresh snapshot and waits for the final
|
|
325
|
+
presentation acknowledgement, including all marker inventory pages; its total
|
|
326
|
+
wait is bounded by `AcknowledgementTimeout`, and cancellation or timeout reports
|
|
327
|
+
failure rather than claiming delivery. An HMP1 replica must also finish consuming
|
|
328
|
+
its ordered output before its HWT1 drain. The demo does this for direct and relay
|
|
329
|
+
views on normal workload exit.
|
|
330
|
+
|
|
331
|
+
HWT1 transfers coalesced terminal state, not every intermediate output event.
|
|
332
|
+
The guarantee is the final retained terminal view, not an unlimited output log.
|
|
333
|
+
Abrupt network loss can retain only the last complete frame actually received;
|
|
334
|
+
it cannot promise the producer's final state. The API does not retain the producer
|
|
335
|
+
after exit.
|
|
301
336
|
|
|
302
337
|
### Browser and deployment requirements
|
|
303
338
|
|
|
@@ -425,6 +460,7 @@ does not claim universal or individually verified bundler support.
|
|
|
425
460
|
| `label` | Accessible label for the terminal's hidden keyboard input. |
|
|
426
461
|
| `onTitleChange` | Initial authoritative workload title, then distinct presented changes; see below. |
|
|
427
462
|
| `onClose` | Native WebSocket close details, including pre-mount transport failure; not workload completion. |
|
|
463
|
+
| `preserveOnDisconnect` | Opt-in final-frame presentation and retained display after transport close, until disposal; defaults to `false`. |
|
|
428
464
|
| `onProgressChange`, `onShellIntegrationChange` | Initial authoritative activity, then distinct presented changes for host-owned chrome. |
|
|
429
465
|
| `onWorkingDirectoryChange`, `onCommandMarkChange` | Initial authoritative OSC 7 directory and latest OSC 133 marker, then distinct presented changes; see below. |
|
|
430
466
|
| `inputBindings`, `onInput`, `actions` | Per-view input policy and custom actions. |
|
package/dist/index.js
CHANGED
|
@@ -557,6 +557,11 @@ function validateMetadata(metadata2) {
|
|
|
557
557
|
if (commandMark !== null) validateCommandMark(commandMark);
|
|
558
558
|
const columns = integer(metadata2.columns, "columns", 1, 1024);
|
|
559
559
|
const rows = integer(metadata2.rows, "rows", 1, 512);
|
|
560
|
+
if (metadata2.lineRenditions !== void 0) {
|
|
561
|
+
array(metadata2.lineRenditions, "line renditions", rows);
|
|
562
|
+
if (metadata2.lineRenditions.length !== rows) throw new Error("Invalid line rendition count");
|
|
563
|
+
for (const rendition of metadata2.lineRenditions) integer(rendition, "line rendition", 0, 3);
|
|
564
|
+
}
|
|
560
565
|
if (typeof metadata2.mouseTracking !== "number" || ![0, 9, 1e3, 1002, 1003].includes(metadata2.mouseTracking)) {
|
|
561
566
|
throw new Error("Unsupported mouse tracking mode");
|
|
562
567
|
}
|
|
@@ -762,9 +767,14 @@ var init_transport_session = __esm({
|
|
|
762
767
|
#ready = Promise.withResolvers();
|
|
763
768
|
#connection;
|
|
764
769
|
#frame = false;
|
|
770
|
+
#receiving;
|
|
771
|
+
#closing = false;
|
|
765
772
|
#sending = false;
|
|
766
773
|
#queue = [];
|
|
767
774
|
#bytes = 0;
|
|
775
|
+
get closing() {
|
|
776
|
+
return this.#closing;
|
|
777
|
+
}
|
|
768
778
|
start(transport2) {
|
|
769
779
|
void this.#connect(transport2);
|
|
770
780
|
}
|
|
@@ -772,16 +782,20 @@ var init_transport_session = __esm({
|
|
|
772
782
|
try {
|
|
773
783
|
const connection = await transport2.connect({
|
|
774
784
|
signal: this.#abort.signal,
|
|
775
|
-
onFrame: (frame) =>
|
|
785
|
+
onFrame: (frame) => {
|
|
786
|
+
const receiving = this.#receive(frame);
|
|
787
|
+
this.#receiving = receiving;
|
|
788
|
+
return receiving;
|
|
789
|
+
},
|
|
776
790
|
onClose: (details) => {
|
|
777
|
-
if (this.#abort.signal.aborted) return;
|
|
791
|
+
if (this.#abort.signal.aborted || this.#closing) return;
|
|
778
792
|
if (!details || typeof details.reason !== "string" || details.code !== void 0 && (!Number.isInteger(details.code) || typeof details.wasClean !== "boolean")) {
|
|
779
793
|
this.#fail(new TypeError("Invalid terminal transport close details"));
|
|
780
794
|
return;
|
|
781
795
|
}
|
|
782
796
|
const snapshot = details.code === void 0 ? { reason: details.reason } : { code: details.code, reason: details.reason, wasClean: details.wasClean };
|
|
783
|
-
this
|
|
784
|
-
this
|
|
797
|
+
this.#closing = true;
|
|
798
|
+
void this.#close(snapshot);
|
|
785
799
|
},
|
|
786
800
|
onError: (error) => this.#fail(error)
|
|
787
801
|
});
|
|
@@ -801,6 +815,7 @@ var init_transport_session = __esm({
|
|
|
801
815
|
async #receive(frame) {
|
|
802
816
|
try {
|
|
803
817
|
this.#abort.signal.throwIfAborted();
|
|
818
|
+
if (this.#closing) throw new Error("Terminal transport is closing");
|
|
804
819
|
if (this.#frame) throw new Error("Concurrent terminal transport frames are not allowed");
|
|
805
820
|
if (!(frame instanceof ArrayBuffer) && !(frame instanceof Uint8Array && frame.buffer instanceof ArrayBuffer))
|
|
806
821
|
throw new TypeError("Transport frames must be ArrayBuffer or Uint8Array backed by ArrayBuffer");
|
|
@@ -818,8 +833,19 @@ var init_transport_session = __esm({
|
|
|
818
833
|
throw error;
|
|
819
834
|
}
|
|
820
835
|
}
|
|
836
|
+
async #close(details) {
|
|
837
|
+
try {
|
|
838
|
+
await this.#receiving;
|
|
839
|
+
} catch {
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
if (this.#abort.signal.aborted) return;
|
|
843
|
+
this.dispose();
|
|
844
|
+
this.callbacks.onClose(details);
|
|
845
|
+
}
|
|
821
846
|
send(control) {
|
|
822
847
|
if (this.#abort.signal.aborted) return Promise.reject(this.#abort.signal.reason);
|
|
848
|
+
if (this.#closing) return Promise.reject(new DOMException("Terminal transport is closing", "AbortError"));
|
|
823
849
|
const bytes = new TextEncoder().encode(control).byteLength;
|
|
824
850
|
if (this.#queue.length >= 256 || this.#bytes + bytes > 1024 * 1024) {
|
|
825
851
|
const error = new Error("Terminal transport control queue exceeded its bounded capacity");
|
|
@@ -837,7 +863,7 @@ var init_transport_session = __esm({
|
|
|
837
863
|
this.#sending = true;
|
|
838
864
|
try {
|
|
839
865
|
await this.#ready.promise;
|
|
840
|
-
while (!this.#abort.signal.aborted && this.#queue.length) {
|
|
866
|
+
while (!this.#abort.signal.aborted && !this.#closing && this.#queue.length) {
|
|
841
867
|
const item = this.#queue[0];
|
|
842
868
|
await this.#connection.send(item.control);
|
|
843
869
|
if (this.#abort.signal.aborted) return;
|
|
@@ -846,7 +872,7 @@ var init_transport_session = __esm({
|
|
|
846
872
|
item.resolve();
|
|
847
873
|
}
|
|
848
874
|
} catch (error) {
|
|
849
|
-
this.#fail(error);
|
|
875
|
+
if (!this.#closing) this.#fail(error);
|
|
850
876
|
} finally {
|
|
851
877
|
this.#sending = false;
|
|
852
878
|
}
|
|
@@ -2154,32 +2180,45 @@ var init_renderer = __esm({
|
|
|
2154
2180
|
[left, top, Math.max(0, right - left), Math.max(0, bottom - top)]
|
|
2155
2181
|
);
|
|
2156
2182
|
}
|
|
2157
|
-
decorations(cell, x, y, width, foreground, underline = cell.underlineColor, clip) {
|
|
2158
|
-
if (cell.attributes & 128) this.solid(x, y + 10, width,
|
|
2159
|
-
if (cell.attributes & 256) this.solid(x, y +
|
|
2183
|
+
decorations(cell, x, y, width, foreground, underline = cell.underlineColor, clip, scaleY = 1) {
|
|
2184
|
+
if (cell.attributes & 128) this.solid(x, y + 10 * scaleY, width, scaleY, foreground, clip);
|
|
2185
|
+
if (cell.attributes & 256) this.solid(x, y + scaleY, width, scaleY, foreground, clip);
|
|
2160
2186
|
const style = cell.underlineStyle || (cell.attributes & 8 ? 1 : 0);
|
|
2161
|
-
this.underline(style, x, y, width, rgba(underline), clip);
|
|
2187
|
+
this.underline(style, x, y, width, rgba(underline), clip, scaleY);
|
|
2162
2188
|
}
|
|
2163
|
-
underline(style, x, y, width, color2, clip) {
|
|
2164
|
-
if (style === 1) this.solid(x, y + 18, width,
|
|
2189
|
+
underline(style, x, y, width, color2, clip, scaleY = 1) {
|
|
2190
|
+
if (style === 1) this.solid(x, y + 18 * scaleY, width, scaleY, color2, clip);
|
|
2165
2191
|
else if (style === 2) {
|
|
2166
|
-
this.solid(x, y + 16, width,
|
|
2167
|
-
this.solid(x, y + 18, width,
|
|
2192
|
+
this.solid(x, y + 16 * scaleY, width, scaleY, color2, clip);
|
|
2193
|
+
this.solid(x, y + 18 * scaleY, width, scaleY, color2, clip);
|
|
2168
2194
|
} else if (style === 3) {
|
|
2169
2195
|
for (let dx = 0; dx < width; dx++) {
|
|
2170
|
-
this.solid(x + dx, y + 17 + Math.round(Math.sin((x + dx) * Math.PI / 4)), 1,
|
|
2196
|
+
this.solid(x + dx, y + (17 + Math.round(Math.sin((x + dx) * Math.PI / 4))) * scaleY, 1, scaleY, color2, clip);
|
|
2171
2197
|
}
|
|
2172
2198
|
} else if (style === 4 || style === 5) {
|
|
2173
2199
|
const step = style === 4 ? 2 : 5;
|
|
2174
2200
|
const segment = style === 4 ? 1 : 3;
|
|
2175
|
-
for (let dx = 0; dx < width; dx += step) this.solid(x + dx, y + 18, Math.min(segment, width - dx),
|
|
2201
|
+
for (let dx = 0; dx < width; dx += step) this.solid(x + dx, y + 18 * scaleY, Math.min(segment, width - dx), scaleY, color2, clip);
|
|
2176
2202
|
}
|
|
2177
2203
|
}
|
|
2178
2204
|
renderCell(cell, index, metadata2, blinkOn, palette2, linkDecoration, selectionClip) {
|
|
2179
2205
|
if (!cell.width || cell.attributes & 64 || cell.attributes & 16 && !blinkOn) return;
|
|
2180
|
-
const
|
|
2181
|
-
const
|
|
2182
|
-
const
|
|
2206
|
+
const row = Math.floor(index / this.columns);
|
|
2207
|
+
const rendition = metadata2.lineRenditions?.[row] ?? 0;
|
|
2208
|
+
const scaleX = rendition ? 2 : 1;
|
|
2209
|
+
const scaleY = rendition >= 2 ? 2 : 1;
|
|
2210
|
+
const column = index % this.columns;
|
|
2211
|
+
if (column >= Math.max(1, Math.floor(this.columns / scaleX))) return;
|
|
2212
|
+
const x = column * CELL_WIDTH * scaleX;
|
|
2213
|
+
const rowY = row * CELL_HEIGHT;
|
|
2214
|
+
const y = rowY - (rendition === 3 ? CELL_HEIGHT : 0);
|
|
2215
|
+
const width = Math.min(cell.width * CELL_WIDTH * scaleX, this.width - x);
|
|
2216
|
+
const clip = !rendition ? selectionClip : selectionClip ? [
|
|
2217
|
+
selectionClip[0],
|
|
2218
|
+
Math.max(rowY, selectionClip[1]),
|
|
2219
|
+
selectionClip[2],
|
|
2220
|
+
Math.min(rowY + CELL_HEIGHT, selectionClip[1] + selectionClip[3]) - Math.max(rowY, selectionClip[1])
|
|
2221
|
+
] : [0, rowY, this.width, CELL_HEIGHT];
|
|
2183
2222
|
const foreground = rgba(selectionClip ? palette2.selectionForeground : foregroundColor(cell, metadata2, palette2));
|
|
2184
2223
|
const glyph = isKgpPlaceholder(cell) ? void 0 : this.glyphs.get(glyphKey(cell));
|
|
2185
2224
|
if (glyph) {
|
|
@@ -2188,12 +2227,12 @@ var init_renderer = __esm({
|
|
|
2188
2227
|
this.atlas,
|
|
2189
2228
|
x,
|
|
2190
2229
|
y,
|
|
2191
|
-
cell.width * CELL_WIDTH,
|
|
2192
|
-
CELL_HEIGHT,
|
|
2230
|
+
cell.width * CELL_WIDTH * scaleX,
|
|
2231
|
+
CELL_HEIGHT * scaleY,
|
|
2193
2232
|
tint,
|
|
2194
2233
|
glyph.colored ? 2 : 1,
|
|
2195
2234
|
[glyph.u0, glyph.v0, glyph.u1, glyph.v1],
|
|
2196
|
-
|
|
2235
|
+
clip
|
|
2197
2236
|
);
|
|
2198
2237
|
}
|
|
2199
2238
|
this.decorations(
|
|
@@ -2203,10 +2242,11 @@ var init_renderer = __esm({
|
|
|
2203
2242
|
width,
|
|
2204
2243
|
foreground,
|
|
2205
2244
|
selectionClip ? palette2.selectionForeground : underlineColor(cell, metadata2, palette2),
|
|
2206
|
-
|
|
2245
|
+
clip,
|
|
2246
|
+
scaleY
|
|
2207
2247
|
);
|
|
2208
2248
|
if (linkDecoration && !cell.underlineStyle && !(cell.attributes & 8) && !isKgpPlaceholder(cell)) {
|
|
2209
|
-
this.underline(linkDecoration, x, y, width, foreground,
|
|
2249
|
+
this.underline(linkDecoration, x, y, width, foreground, clip, scaleY);
|
|
2210
2250
|
}
|
|
2211
2251
|
}
|
|
2212
2252
|
render(cells2, metadata2, blinkOn, linkDecorations, palette2 = DEFAULT_PALETTE) {
|
|
@@ -2222,10 +2262,13 @@ var init_renderer = __esm({
|
|
|
2222
2262
|
for (let i = 0; i < cells2.length; i++) {
|
|
2223
2263
|
const cell = cells2[i];
|
|
2224
2264
|
if (!cell) continue;
|
|
2265
|
+
const row = Math.floor(i / this.columns);
|
|
2266
|
+
const scaleX = metadata2.lineRenditions?.[row] ? 2 : 1;
|
|
2267
|
+
if (i % this.columns >= Math.max(1, Math.floor(this.columns / scaleX))) continue;
|
|
2225
2268
|
this.solid(
|
|
2226
|
-
i % this.columns * CELL_WIDTH,
|
|
2227
|
-
|
|
2228
|
-
CELL_WIDTH,
|
|
2269
|
+
i % this.columns * CELL_WIDTH * scaleX,
|
|
2270
|
+
row * CELL_HEIGHT,
|
|
2271
|
+
CELL_WIDTH * scaleX,
|
|
2229
2272
|
CELL_HEIGHT,
|
|
2230
2273
|
rgba(backgroundColor(cell, metadata2, palette2))
|
|
2231
2274
|
);
|
|
@@ -2239,10 +2282,11 @@ var init_renderer = __esm({
|
|
|
2239
2282
|
const selection = metadata2.history?.selection;
|
|
2240
2283
|
if (selection?.status === "valid") {
|
|
2241
2284
|
for (const range of selection.ranges) {
|
|
2285
|
+
const scaleX = metadata2.lineRenditions?.[range.row] ? 2 : 1;
|
|
2242
2286
|
const clip = [
|
|
2243
|
-
range.startColumn * CELL_WIDTH,
|
|
2287
|
+
range.startColumn * CELL_WIDTH * scaleX,
|
|
2244
2288
|
range.row * CELL_HEIGHT,
|
|
2245
|
-
(range.endColumn - range.startColumn) * CELL_WIDTH,
|
|
2289
|
+
(range.endColumn - range.startColumn) * CELL_WIDTH * scaleX,
|
|
2246
2290
|
CELL_HEIGHT
|
|
2247
2291
|
];
|
|
2248
2292
|
this.solid(...clip, rgba(palette2.selectionBackground));
|
|
@@ -2260,13 +2304,14 @@ var init_renderer = __esm({
|
|
|
2260
2304
|
if (cursor.visible && (!cursorBlink || blinkOn) && cursor.x >= 0 && cursor.x < this.columns && cursor.y >= 0 && cursor.y < this.rows) {
|
|
2261
2305
|
const cell = cells2[cursor.y * this.columns + cursor.x];
|
|
2262
2306
|
const color2 = rgba(palette2.cursor ?? (cell ? foregroundColor(cell, metadata2, palette2) : palette2.foreground));
|
|
2263
|
-
const
|
|
2307
|
+
const scaleX = metadata2.lineRenditions?.[cursor.y] ? 2 : 1;
|
|
2308
|
+
const x = cursor.x * CELL_WIDTH * scaleX;
|
|
2264
2309
|
const y = cursor.y * CELL_HEIGHT;
|
|
2265
|
-
if (cursor.shape === 3 || cursor.shape === 4) this.solid(x, y + 18, CELL_WIDTH, 2, color2);
|
|
2310
|
+
if (cursor.shape === 3 || cursor.shape === 4) this.solid(x, y + 18, CELL_WIDTH * scaleX, 2, color2);
|
|
2266
2311
|
else if (cursor.shape === 5 || cursor.shape === 6) this.solid(x, y, 2, CELL_HEIGHT, color2);
|
|
2267
2312
|
else {
|
|
2268
2313
|
color2[3] *= 0.55;
|
|
2269
|
-
this.solid(x, y, CELL_WIDTH, CELL_HEIGHT, color2);
|
|
2314
|
+
this.solid(x, y, CELL_WIDTH * scaleX, CELL_HEIGHT, color2);
|
|
2270
2315
|
}
|
|
2271
2316
|
}
|
|
2272
2317
|
const base = rgba(metadata2.colorEncoding === "indexed-v1" ? palette2.background : metadata2.defaultBackground ?? cells2[0]?.background ?? 4278190080);
|
|
@@ -2479,10 +2524,21 @@ function postStatus(message, level = "info") {
|
|
|
2479
2524
|
self.postMessage({ type: "status", message, level });
|
|
2480
2525
|
}
|
|
2481
2526
|
function send(message) {
|
|
2482
|
-
if (stats.connected && !failed && !stopped)
|
|
2527
|
+
if (stats.connected && !disconnected && !transport?.closing && !failed && !stopped) {
|
|
2528
|
+
void transport?.send(JSON.stringify(message)).catch((error) => {
|
|
2529
|
+
if (!transport?.closing) fail(error);
|
|
2530
|
+
});
|
|
2531
|
+
}
|
|
2483
2532
|
}
|
|
2484
2533
|
function emitStats(text) {
|
|
2485
2534
|
if (renderer && !renderer.disposed) Object.assign(stats, renderer.metrics());
|
|
2535
|
+
else if (renderer?.disposed) Object.assign(stats, {
|
|
2536
|
+
imageCount: 0,
|
|
2537
|
+
textureBytes: 0,
|
|
2538
|
+
atlasGlyphs: 0,
|
|
2539
|
+
atlasBytes: 0,
|
|
2540
|
+
instanceBufferBytes: 0
|
|
2541
|
+
});
|
|
2486
2542
|
self.postMessage({ type: "stats", stats: { ...stats }, ...text === void 0 ? {} : { text } });
|
|
2487
2543
|
}
|
|
2488
2544
|
function fail(error) {
|
|
@@ -2500,11 +2556,11 @@ function fail(error) {
|
|
|
2500
2556
|
}
|
|
2501
2557
|
function scheduleRender() {
|
|
2502
2558
|
needsRender = true;
|
|
2503
|
-
if (scheduled || drawing || processing || failed || stopped || !metadata || markerPages.pending || awaitingFull) return;
|
|
2559
|
+
if (scheduled || drawing || processing || failed || stopped || disconnected && stats.connected || !metadata || markerPages.pending || awaitingFull) return;
|
|
2504
2560
|
scheduled = true;
|
|
2505
2561
|
self.requestAnimationFrame(() => {
|
|
2506
2562
|
scheduled = false;
|
|
2507
|
-
if (processing || drawing || failed || stopped || markerPages.pending || awaitingFull) return;
|
|
2563
|
+
if (processing || drawing || failed || stopped || disconnected && stats.connected || markerPages.pending || awaitingFull) return;
|
|
2508
2564
|
renderPromise = drawFrame();
|
|
2509
2565
|
});
|
|
2510
2566
|
}
|
|
@@ -2517,7 +2573,7 @@ async function drawFrame() {
|
|
|
2517
2573
|
if (frame) links.prepare(cells, metadata);
|
|
2518
2574
|
const linkSubmission = links.submission();
|
|
2519
2575
|
renderer.resize(metadata.columns, metadata.rows, viewport);
|
|
2520
|
-
const blink = Math.floor(performance.now() / 600) % 2 === 0;
|
|
2576
|
+
const blink = disconnected || Math.floor(performance.now() / 600) % 2 === 0;
|
|
2521
2577
|
const result = renderer.render(cells, metadata, blink, linkSubmission.mask, palette);
|
|
2522
2578
|
await renderer.idle();
|
|
2523
2579
|
if (failed || stopped) return;
|
|
@@ -2562,6 +2618,7 @@ async function drawFrame() {
|
|
|
2562
2618
|
commandMark: metadata.commandMark,
|
|
2563
2619
|
text,
|
|
2564
2620
|
hyperlinks: metadata.hyperlinks,
|
|
2621
|
+
lineRenditions: metadata.lineRenditions,
|
|
2565
2622
|
...links.present(cells, metadata)
|
|
2566
2623
|
});
|
|
2567
2624
|
send({ type: "ack", revision: frame.revision });
|
|
@@ -2580,7 +2637,7 @@ async function drawFrame() {
|
|
|
2580
2637
|
}
|
|
2581
2638
|
}
|
|
2582
2639
|
async function receiveFrame(buffer) {
|
|
2583
|
-
if (failed || stopped || !renderer) return;
|
|
2640
|
+
if (failed || stopped || disconnected || !renderer) return;
|
|
2584
2641
|
if (frameInFlight) throw new Error("Server sent a second state frame before acknowledgement");
|
|
2585
2642
|
frameInFlight = true;
|
|
2586
2643
|
processing = true;
|
|
@@ -2641,6 +2698,7 @@ async function receiveFrame(buffer) {
|
|
|
2641
2698
|
}
|
|
2642
2699
|
async function initialize(message) {
|
|
2643
2700
|
if (renderer || transport) throw new Error("Worker is already initialized");
|
|
2701
|
+
preserveOnDisconnect = message.preserveOnDisconnect ?? false;
|
|
2644
2702
|
palette = compilePalette(normalizePalette(message.palette === void 0 ? defaultDarkPalette : message.palette));
|
|
2645
2703
|
if (typeof self.requestAnimationFrame !== "function") {
|
|
2646
2704
|
throw new Error("This browser does not support requestAnimationFrame in a dedicated OffscreenCanvas worker");
|
|
@@ -2668,17 +2726,31 @@ async function initialize(message) {
|
|
|
2668
2726
|
onFrame: receiveFrame,
|
|
2669
2727
|
onError: fail,
|
|
2670
2728
|
onClose(details) {
|
|
2671
|
-
if (failed || stopped) return;
|
|
2672
|
-
|
|
2673
|
-
stats.gpu = "stopped";
|
|
2674
|
-
stats.fps = 0;
|
|
2675
|
-
stopped = true;
|
|
2729
|
+
if (failed || stopped || disconnected) return;
|
|
2730
|
+
disconnected = true;
|
|
2676
2731
|
clearInterval(metricsTimer);
|
|
2677
2732
|
clearInterval(blinkTimer);
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2733
|
+
void finishClose().catch(fail);
|
|
2734
|
+
async function finishClose() {
|
|
2735
|
+
if (preserveOnDisconnect) {
|
|
2736
|
+
await renderPromise;
|
|
2737
|
+
if (failed || stopped) return;
|
|
2738
|
+
if ((needsRender || hasBlink && !lastBlink) && !markerPages.pending && !awaitingFull) {
|
|
2739
|
+
needsRender = true;
|
|
2740
|
+
await drawFrame();
|
|
2741
|
+
}
|
|
2742
|
+
if (failed || stopped) return;
|
|
2743
|
+
} else {
|
|
2744
|
+
stopped = true;
|
|
2745
|
+
renderer?.dispose();
|
|
2746
|
+
}
|
|
2747
|
+
stats.connected = false;
|
|
2748
|
+
stats.gpu = "stopped";
|
|
2749
|
+
stats.fps = 0;
|
|
2750
|
+
self.postMessage({ type: "closed", details });
|
|
2751
|
+
postStatus(`View disconnected (${details.code === void 0 ? details.reason : `${details.code}${details.reason ? `: ${details.reason}` : ""}`}). Attach another view to reconnect.`, "error");
|
|
2752
|
+
emitStats();
|
|
2753
|
+
}
|
|
2682
2754
|
}
|
|
2683
2755
|
});
|
|
2684
2756
|
metricsTimer = setInterval(() => {
|
|
@@ -2707,7 +2779,7 @@ async function initialize(message) {
|
|
|
2707
2779
|
}
|
|
2708
2780
|
});
|
|
2709
2781
|
}
|
|
2710
|
-
var renderer, transport, bridge, bridgeReady, bridgeSent, failed, stopped, processing, drawing, frameInFlight, scheduled, needsRender, hasBlink, lastBlink, metadata, cells, localRevision, pendingFrame, renderPromise, metricsTimer, blinkTimer, viewport, links, markerPages, awaitingFull, requestedColorEncoding, palette, stats, sample;
|
|
2782
|
+
var renderer, transport, bridge, bridgeReady, bridgeSent, failed, stopped, disconnected, preserveOnDisconnect, processing, drawing, frameInFlight, scheduled, needsRender, hasBlink, lastBlink, metadata, cells, localRevision, pendingFrame, renderPromise, metricsTimer, blinkTimer, viewport, links, markerPages, awaitingFull, requestedColorEncoding, palette, stats, sample;
|
|
2711
2783
|
var init_terminal_worker = __esm({
|
|
2712
2784
|
"src/terminal-worker.ts"() {
|
|
2713
2785
|
"use strict";
|
|
@@ -2721,6 +2793,8 @@ var init_terminal_worker = __esm({
|
|
|
2721
2793
|
init_transport_session();
|
|
2722
2794
|
failed = false;
|
|
2723
2795
|
stopped = false;
|
|
2796
|
+
disconnected = false;
|
|
2797
|
+
preserveOnDisconnect = false;
|
|
2724
2798
|
processing = false;
|
|
2725
2799
|
drawing = false;
|
|
2726
2800
|
frameInFlight = false;
|
|
@@ -2822,7 +2896,7 @@ var init_terminal_worker = __esm({
|
|
|
2822
2896
|
} catch (error) {
|
|
2823
2897
|
fail(error);
|
|
2824
2898
|
}
|
|
2825
|
-
} else if (message.type === "linkDetection" && !failed && !stopped) {
|
|
2899
|
+
} else if (message.type === "linkDetection" && !failed && !stopped && !disconnected) {
|
|
2826
2900
|
try {
|
|
2827
2901
|
if (!links.configure(message.enabled, message.generation)) return;
|
|
2828
2902
|
if (!processing && !drawing && !pendingFrame) {
|
|
@@ -2833,7 +2907,7 @@ var init_terminal_worker = __esm({
|
|
|
2833
2907
|
} catch (error) {
|
|
2834
2908
|
fail(error);
|
|
2835
2909
|
}
|
|
2836
|
-
} else if (message.type === "linkDecorations" && !failed && !stopped) {
|
|
2910
|
+
} else if (message.type === "linkDecorations" && !failed && !stopped && !disconnected) {
|
|
2837
2911
|
try {
|
|
2838
2912
|
if (links.accept(
|
|
2839
2913
|
message.revision,
|
|
@@ -3199,7 +3273,10 @@ function captureMouse(canvas, send2, focus, inspection = {}) {
|
|
|
3199
3273
|
const originalCursor = canvas.style.cursor;
|
|
3200
3274
|
const state = () => ({ tracking, ...inspection.state?.() });
|
|
3201
3275
|
function point(event, clamp2 = false) {
|
|
3202
|
-
|
|
3276
|
+
const position = cellPoint(event, canvas.getBoundingClientRect(), columns, rows, clamp2);
|
|
3277
|
+
if (position && inspection.lineRenditions?.()[position.y])
|
|
3278
|
+
position.x = Math.min(Math.max(0, Math.floor(columns / 2) - 1), Math.floor(position.x / 2));
|
|
3279
|
+
return position;
|
|
3203
3280
|
}
|
|
3204
3281
|
function refreshHover(modifiers = hoverModifiers) {
|
|
3205
3282
|
if (!inspection.hyperlink) return;
|
|
@@ -5468,6 +5545,7 @@ var WebTerminal = class _WebTerminal {
|
|
|
5468
5545
|
#connected = false;
|
|
5469
5546
|
#closed = false;
|
|
5470
5547
|
#readOnly;
|
|
5548
|
+
#preserveOnDisconnect;
|
|
5471
5549
|
#disposed = false;
|
|
5472
5550
|
#hasGeometry = false;
|
|
5473
5551
|
#resizeTimer;
|
|
@@ -5508,6 +5586,7 @@ var WebTerminal = class _WebTerminal {
|
|
|
5508
5586
|
#selectionUIError = "";
|
|
5509
5587
|
#canvasSize = { width: 0, height: 0 };
|
|
5510
5588
|
#hyperlinks = new Hyperlinks();
|
|
5589
|
+
#lineRenditions = [];
|
|
5511
5590
|
#links;
|
|
5512
5591
|
#linkDetector;
|
|
5513
5592
|
#linkGeneration = 1;
|
|
@@ -5546,6 +5625,9 @@ var WebTerminal = class _WebTerminal {
|
|
|
5546
5625
|
if (options.readOnly !== void 0 && typeof options.readOnly !== "boolean")
|
|
5547
5626
|
throw new TypeError("readOnly must be a boolean");
|
|
5548
5627
|
this.#readOnly = options.readOnly ?? false;
|
|
5628
|
+
if (options.preserveOnDisconnect !== void 0 && typeof options.preserveOnDisconnect !== "boolean")
|
|
5629
|
+
throw new TypeError("preserveOnDisconnect must be a boolean");
|
|
5630
|
+
this.#preserveOnDisconnect = options.preserveOnDisconnect ?? false;
|
|
5549
5631
|
this.#renderer = normalizeRenderer(options.renderer);
|
|
5550
5632
|
this.#colorMode = normalizeColorMode(options.colorMode);
|
|
5551
5633
|
this.#palettes = {
|
|
@@ -5796,6 +5878,7 @@ var WebTerminal = class _WebTerminal {
|
|
|
5796
5878
|
}
|
|
5797
5879
|
};
|
|
5798
5880
|
this.#mouse = captureMouse(this.#canvas, (command) => this.#inputCommand(command), () => this.focus(), {
|
|
5881
|
+
lineRenditions: () => this.#lineRenditions,
|
|
5799
5882
|
state: () => ({
|
|
5800
5883
|
historical: !this.viewport.following || this.viewport.pending,
|
|
5801
5884
|
readOnly: this.#readOnly,
|
|
@@ -5859,7 +5942,8 @@ var WebTerminal = class _WebTerminal {
|
|
|
5859
5942
|
scale,
|
|
5860
5943
|
font,
|
|
5861
5944
|
renderer: this.#renderer,
|
|
5862
|
-
palette: this.#palettes[this.resolvedColorMode]
|
|
5945
|
+
palette: this.#palettes[this.resolvedColorMode],
|
|
5946
|
+
preserveOnDisconnect: this.#preserveOnDisconnect
|
|
5863
5947
|
}, [canvas]);
|
|
5864
5948
|
this.#applyPalette();
|
|
5865
5949
|
this.#postLinkConfiguration();
|
|
@@ -5884,7 +5968,8 @@ var WebTerminal = class _WebTerminal {
|
|
|
5884
5968
|
this.#transportSession.start(this.#transport);
|
|
5885
5969
|
} else if (message.type === "transportSend") {
|
|
5886
5970
|
void Promise.resolve(this.#transportFrame?.promise).then(() => this.#transportSession?.send(message.control)).then(() => this.#post({ type: "transportSent" })).catch((error) => {
|
|
5887
|
-
if (!this.#disposed
|
|
5971
|
+
if (!this.#disposed && !this.#transportSession?.closing)
|
|
5972
|
+
this.#post({ type: "transportError", message: errorMessage(error) });
|
|
5888
5973
|
});
|
|
5889
5974
|
} else if (message.type === "transportReceived") {
|
|
5890
5975
|
this.#transportFrame?.resolve();
|
|
@@ -5914,6 +5999,7 @@ var WebTerminal = class _WebTerminal {
|
|
|
5914
5999
|
}
|
|
5915
6000
|
this.#options.onStatus?.(message.message, message.level);
|
|
5916
6001
|
} else if (message.type === "geometry") {
|
|
6002
|
+
this.#lineRenditions = message.lineRenditions ?? [];
|
|
5917
6003
|
this.#linkRevision = message.revision;
|
|
5918
6004
|
this.#osc8Rows.clear();
|
|
5919
6005
|
for (const range of message.hyperlinks) {
|
|
@@ -6075,7 +6161,8 @@ var WebTerminal = class _WebTerminal {
|
|
|
6075
6161
|
const element = document.createElement("span");
|
|
6076
6162
|
element.className = "highlight";
|
|
6077
6163
|
element.setAttribute("part", "selection-highlight");
|
|
6078
|
-
|
|
6164
|
+
const scaleX = this.#lineRenditions[range.row] ? 2 : 1;
|
|
6165
|
+
element.style.cssText = `left:${range.startColumn * scaleX / this.#geometry.columns * 100}%;top:${range.row / this.#geometry.rows * 100}%;width:${(range.endColumn - range.startColumn) * scaleX / this.#geometry.columns * 100}%;height:${100 / this.#geometry.rows}%`;
|
|
6079
6166
|
return element;
|
|
6080
6167
|
}));
|
|
6081
6168
|
const live = requiredElement(this.#inspection, ".return-live", HTMLButtonElement);
|