@aitty/browser 0.1.0
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/dist/browser.d.ts +6 -0
- package/dist/browser.js +5 -0
- package/dist/frontend/ansi-sequences.d.ts +5 -0
- package/dist/frontend/ansi-sequences.js +17 -0
- package/dist/frontend/ansi-style-tracker.d.ts +37 -0
- package/dist/frontend/ansi-style-tracker.js +435 -0
- package/dist/frontend/browser-terminal-renderer.d.ts +86 -0
- package/dist/frontend/browser-terminal-renderer.js +546 -0
- package/dist/frontend/cell-width.d.ts +5 -0
- package/dist/frontend/cell-width.js +9 -0
- package/dist/frontend/terminal-app.d.ts +156 -0
- package/dist/frontend/terminal-app.js +1957 -0
- package/dist/frontend/terminal-input-policies.d.ts +24 -0
- package/dist/frontend/terminal-input-policies.js +462 -0
- package/dist/frontend/terminal-theme-protocol.d.ts +22 -0
- package/dist/frontend/terminal-theme-protocol.js +270 -0
- package/dist/frontend/terminal.css +268 -0
- package/dist/theme-source.d.ts +2 -0
- package/dist/theme-source.js +2 -0
- package/package.json +54 -0
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
import { isWideCodePoint } from "./cell-width.js";
|
|
2
|
+
//#region src/frontend/browser-terminal-renderer.ts
|
|
3
|
+
const DEFAULT_COLOR = 256;
|
|
4
|
+
const FLAG_BOLD = 1;
|
|
5
|
+
const FLAG_DIM = 2;
|
|
6
|
+
const FLAG_ITALIC = 4;
|
|
7
|
+
const FLAG_UNDERLINE = 8;
|
|
8
|
+
const FLAG_REVERSE = 32;
|
|
9
|
+
const FLAG_INVISIBLE = 64;
|
|
10
|
+
const FLAG_STRIKETHROUGH = 128;
|
|
11
|
+
function colorToCSS(index) {
|
|
12
|
+
if (index === DEFAULT_COLOR) return null;
|
|
13
|
+
if (index < 16) return `var(--term-color-${index})`;
|
|
14
|
+
if (index < 232) {
|
|
15
|
+
const value = index - 16;
|
|
16
|
+
return `rgb(${Math.floor(value / 36) * 51}, ${Math.floor(value / 6) % 6 * 51}, ${value % 6 * 51})`;
|
|
17
|
+
}
|
|
18
|
+
const level = (index - 232) * 10 + 8;
|
|
19
|
+
return `rgb(${level}, ${level}, ${level})`;
|
|
20
|
+
}
|
|
21
|
+
function resolveColorChannels(fg, bg, flags, override = null) {
|
|
22
|
+
let resolvedFg = override?.fg ?? colorToCSS(fg);
|
|
23
|
+
let resolvedBg = override?.bg ?? colorToCSS(bg);
|
|
24
|
+
if (flags & FLAG_REVERSE) {
|
|
25
|
+
[resolvedFg, resolvedBg] = [resolvedBg, resolvedFg];
|
|
26
|
+
if (resolvedFg === null) resolvedFg = colorToCSS(0);
|
|
27
|
+
if (resolvedBg === null) resolvedBg = colorToCSS(7);
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
bg: resolvedBg,
|
|
31
|
+
fg: resolvedFg
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function buildCellStyle(fg, bg, flags, override = null) {
|
|
35
|
+
const colors = resolveColorChannels(fg, bg, flags, override);
|
|
36
|
+
let style = "";
|
|
37
|
+
if (colors.fg) style += `color:${colors.fg};`;
|
|
38
|
+
if (colors.bg) style += `background:${colors.bg};`;
|
|
39
|
+
if (flags & FLAG_BOLD) style += "font-weight:bold;";
|
|
40
|
+
if (flags & FLAG_DIM) style += "opacity:0.5;";
|
|
41
|
+
if (flags & FLAG_ITALIC) style += "font-style:italic;";
|
|
42
|
+
const decorations = [];
|
|
43
|
+
if (flags & FLAG_UNDERLINE) decorations.push("underline");
|
|
44
|
+
if (flags & FLAG_STRIKETHROUGH) decorations.push("line-through");
|
|
45
|
+
if (decorations.length > 0) style += `text-decoration:${decorations.join(" ")};`;
|
|
46
|
+
if (flags & FLAG_INVISIBLE) style += "visibility:hidden;";
|
|
47
|
+
return style;
|
|
48
|
+
}
|
|
49
|
+
function appendRun(parent, text, style, className = "") {
|
|
50
|
+
const span = document.createElement("span");
|
|
51
|
+
if (className) span.className = className;
|
|
52
|
+
if (style) span.style.cssText = style;
|
|
53
|
+
span.textContent = text;
|
|
54
|
+
parent.appendChild(span);
|
|
55
|
+
}
|
|
56
|
+
function appendWideCell(parent, text, style, cursor = false) {
|
|
57
|
+
const span = document.createElement("span");
|
|
58
|
+
span.className = cursor ? "term-wide term-cursor" : "term-wide";
|
|
59
|
+
if (style) span.style.cssText = style;
|
|
60
|
+
span.style.width = "2ch";
|
|
61
|
+
span.style.minWidth = "2ch";
|
|
62
|
+
span.style.maxWidth = "2ch";
|
|
63
|
+
span.style.overflow = "hidden";
|
|
64
|
+
span.textContent = text;
|
|
65
|
+
parent.appendChild(span);
|
|
66
|
+
}
|
|
67
|
+
function isWideContinuationCell(cell) {
|
|
68
|
+
return (cell?.char ?? 0) === 0;
|
|
69
|
+
}
|
|
70
|
+
function serializeRowText(getCell, lineLength) {
|
|
71
|
+
let text = "";
|
|
72
|
+
for (let col = 0; col < lineLength; col += 1) {
|
|
73
|
+
const codePoint = getCell(col)?.char ?? 0;
|
|
74
|
+
if (isWideCodePoint(codePoint)) {
|
|
75
|
+
text += String.fromCodePoint(codePoint);
|
|
76
|
+
if (col + 1 < lineLength && isWideContinuationCell(getCell(col + 1))) col += 1;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
text += codePoint >= 32 ? String.fromCodePoint(codePoint) : " ";
|
|
80
|
+
}
|
|
81
|
+
return normalizeRowText(text);
|
|
82
|
+
}
|
|
83
|
+
function normalizeRowText(text) {
|
|
84
|
+
return text.trimEnd();
|
|
85
|
+
}
|
|
86
|
+
function cloneSnapshotRow(snapshot) {
|
|
87
|
+
const rowElement = snapshot.element.cloneNode(true);
|
|
88
|
+
rowElement.className = "term-row term-scrollback-row term-restored-scrollback-row";
|
|
89
|
+
rowElement.querySelectorAll(".term-cursor").forEach((cursor) => {
|
|
90
|
+
cursor.classList.remove("term-cursor");
|
|
91
|
+
});
|
|
92
|
+
return rowElement;
|
|
93
|
+
}
|
|
94
|
+
function resolveSnapshotPrefixMatch(snapshotRows, currentRows) {
|
|
95
|
+
const meaningfulSnapshotRows = snapshotRows.filter((row) => row.text.length > 0);
|
|
96
|
+
const meaningfulCurrentRows = currentRows.filter((row) => row.length > 0);
|
|
97
|
+
if (meaningfulSnapshotRows.length === 0) return {
|
|
98
|
+
missingPrefixCount: 0,
|
|
99
|
+
overlapCount: 0
|
|
100
|
+
};
|
|
101
|
+
if (meaningfulCurrentRows.length === 0) return {
|
|
102
|
+
missingPrefixCount: meaningfulSnapshotRows.length,
|
|
103
|
+
overlapCount: 0
|
|
104
|
+
};
|
|
105
|
+
let bestStart = meaningfulSnapshotRows.length;
|
|
106
|
+
let bestOverlap = 0;
|
|
107
|
+
for (let start = 0; start < meaningfulSnapshotRows.length; start += 1) {
|
|
108
|
+
let overlap = 0;
|
|
109
|
+
while (start + overlap < meaningfulSnapshotRows.length && overlap < meaningfulCurrentRows.length && meaningfulSnapshotRows[start + overlap].text === meaningfulCurrentRows[overlap]) overlap += 1;
|
|
110
|
+
if (overlap > bestOverlap) {
|
|
111
|
+
bestOverlap = overlap;
|
|
112
|
+
bestStart = start;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (bestOverlap === 0) return {
|
|
116
|
+
missingPrefixCount: 0,
|
|
117
|
+
overlapCount: 0
|
|
118
|
+
};
|
|
119
|
+
return {
|
|
120
|
+
missingPrefixCount: bestStart,
|
|
121
|
+
overlapCount: bestOverlap
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function findRowSequence(rows, sequence, minStart = 0) {
|
|
125
|
+
if (sequence.length === 0) return Math.max(0, minStart);
|
|
126
|
+
outer: for (let start = Math.max(0, minStart); start <= rows.length - sequence.length; start += 1) {
|
|
127
|
+
for (let index = 0; index < sequence.length; index += 1) if (rows[start + index] !== sequence[index]) continue outer;
|
|
128
|
+
return start;
|
|
129
|
+
}
|
|
130
|
+
return -1;
|
|
131
|
+
}
|
|
132
|
+
function resolveColors(fg, bg, flags, override = null) {
|
|
133
|
+
const colors = resolveColorChannels(fg, bg, flags, override);
|
|
134
|
+
return {
|
|
135
|
+
bg: colors.bg ?? "var(--term-bg)",
|
|
136
|
+
fg: colors.fg ?? "var(--term-fg)"
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function getBlockBackground(codePoint, fg, bg) {
|
|
140
|
+
switch (codePoint) {
|
|
141
|
+
case 9600: return `linear-gradient(${fg} 50%,${bg} 50%)`;
|
|
142
|
+
case 9601: return `linear-gradient(${bg} 87.5%,${fg} 87.5%)`;
|
|
143
|
+
case 9602: return `linear-gradient(${bg} 75%,${fg} 75%)`;
|
|
144
|
+
case 9603: return `linear-gradient(${bg} 62.5%,${fg} 62.5%)`;
|
|
145
|
+
case 9604: return `linear-gradient(${bg} 50%,${fg} 50%)`;
|
|
146
|
+
case 9605: return `linear-gradient(${bg} 37.5%,${fg} 37.5%)`;
|
|
147
|
+
case 9606: return `linear-gradient(${bg} 25%,${fg} 25%)`;
|
|
148
|
+
case 9607: return `linear-gradient(${bg} 12.5%,${fg} 12.5%)`;
|
|
149
|
+
case 9608: return fg;
|
|
150
|
+
case 9609: return `linear-gradient(to right,${fg} 87.5%,${bg} 87.5%)`;
|
|
151
|
+
case 9610: return `linear-gradient(to right,${fg} 75%,${bg} 75%)`;
|
|
152
|
+
case 9611: return `linear-gradient(to right,${fg} 62.5%,${bg} 62.5%)`;
|
|
153
|
+
case 9612: return `linear-gradient(to right,${fg} 50%,${bg} 50%)`;
|
|
154
|
+
case 9613: return `linear-gradient(to right,${fg} 37.5%,${bg} 37.5%)`;
|
|
155
|
+
case 9614: return `linear-gradient(to right,${fg} 25%,${bg} 25%)`;
|
|
156
|
+
case 9615: return `linear-gradient(to right,${fg} 12.5%,${bg} 12.5%)`;
|
|
157
|
+
case 9616: return `linear-gradient(to right,${bg} 50%,${fg} 50%)`;
|
|
158
|
+
case 9617: return `color-mix(in srgb,${fg} 25%,${bg})`;
|
|
159
|
+
case 9618: return `color-mix(in srgb,${fg} 50%,${bg})`;
|
|
160
|
+
case 9619: return `color-mix(in srgb,${fg} 75%,${bg})`;
|
|
161
|
+
case 9620: return `linear-gradient(${fg} 12.5%,${bg} 12.5%)`;
|
|
162
|
+
case 9621: return `linear-gradient(to right,${bg} 87.5%,${fg} 87.5%)`;
|
|
163
|
+
default: {
|
|
164
|
+
const cells = {
|
|
165
|
+
9622: [
|
|
166
|
+
false,
|
|
167
|
+
false,
|
|
168
|
+
true,
|
|
169
|
+
false
|
|
170
|
+
],
|
|
171
|
+
9623: [
|
|
172
|
+
false,
|
|
173
|
+
false,
|
|
174
|
+
false,
|
|
175
|
+
true
|
|
176
|
+
],
|
|
177
|
+
9624: [
|
|
178
|
+
true,
|
|
179
|
+
false,
|
|
180
|
+
false,
|
|
181
|
+
false
|
|
182
|
+
],
|
|
183
|
+
9625: [
|
|
184
|
+
true,
|
|
185
|
+
false,
|
|
186
|
+
true,
|
|
187
|
+
true
|
|
188
|
+
],
|
|
189
|
+
9626: [
|
|
190
|
+
true,
|
|
191
|
+
false,
|
|
192
|
+
false,
|
|
193
|
+
true
|
|
194
|
+
],
|
|
195
|
+
9627: [
|
|
196
|
+
true,
|
|
197
|
+
true,
|
|
198
|
+
true,
|
|
199
|
+
false
|
|
200
|
+
],
|
|
201
|
+
9628: [
|
|
202
|
+
true,
|
|
203
|
+
true,
|
|
204
|
+
false,
|
|
205
|
+
true
|
|
206
|
+
],
|
|
207
|
+
9629: [
|
|
208
|
+
false,
|
|
209
|
+
true,
|
|
210
|
+
false,
|
|
211
|
+
false
|
|
212
|
+
],
|
|
213
|
+
9630: [
|
|
214
|
+
false,
|
|
215
|
+
true,
|
|
216
|
+
true,
|
|
217
|
+
false
|
|
218
|
+
],
|
|
219
|
+
9631: [
|
|
220
|
+
false,
|
|
221
|
+
true,
|
|
222
|
+
true,
|
|
223
|
+
true
|
|
224
|
+
]
|
|
225
|
+
}[codePoint];
|
|
226
|
+
if (!cells) return fg;
|
|
227
|
+
const [topLeft, topRight, bottomLeft, bottomRight] = cells;
|
|
228
|
+
const layers = [];
|
|
229
|
+
const positions = [
|
|
230
|
+
"0 0",
|
|
231
|
+
"100% 0",
|
|
232
|
+
"0 100%",
|
|
233
|
+
"100% 100%"
|
|
234
|
+
];
|
|
235
|
+
[
|
|
236
|
+
topLeft,
|
|
237
|
+
topRight,
|
|
238
|
+
bottomLeft,
|
|
239
|
+
bottomRight
|
|
240
|
+
].forEach((filled, index) => {
|
|
241
|
+
if (filled) layers.push(`linear-gradient(${fg},${fg}) ${positions[index]}/50% 50% no-repeat`);
|
|
242
|
+
});
|
|
243
|
+
layers.push(bg);
|
|
244
|
+
return layers.join(",");
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
var BrowserTerminalRenderer = class {
|
|
249
|
+
_altScreenMeaningfulScrollbackCount;
|
|
250
|
+
_altScreenVisibleRowSnapshot;
|
|
251
|
+
_lastAltScreenState;
|
|
252
|
+
_renderedScrollbackCount;
|
|
253
|
+
_restoreSnapshotCount;
|
|
254
|
+
_scrollbackRowEls;
|
|
255
|
+
cols;
|
|
256
|
+
container;
|
|
257
|
+
prevContainerBg;
|
|
258
|
+
prevCursorCol;
|
|
259
|
+
prevCursorRow;
|
|
260
|
+
prevCursorVisible;
|
|
261
|
+
prevRowBg;
|
|
262
|
+
rowEls;
|
|
263
|
+
rows;
|
|
264
|
+
styleTracker;
|
|
265
|
+
constructor(container, styleTracker) {
|
|
266
|
+
this._altScreenVisibleRowSnapshot = [];
|
|
267
|
+
this._altScreenMeaningfulScrollbackCount = 0;
|
|
268
|
+
this._lastAltScreenState = false;
|
|
269
|
+
this._restoreSnapshotCount = 0;
|
|
270
|
+
this.cols = 0;
|
|
271
|
+
this.container = container;
|
|
272
|
+
this.prevContainerBg = "";
|
|
273
|
+
this.prevCursorCol = -1;
|
|
274
|
+
this.prevCursorRow = -1;
|
|
275
|
+
this.prevCursorVisible = false;
|
|
276
|
+
this.prevRowBg = [];
|
|
277
|
+
this.rowEls = [];
|
|
278
|
+
this.rows = 0;
|
|
279
|
+
this.styleTracker = styleTracker;
|
|
280
|
+
this._renderedScrollbackCount = 0;
|
|
281
|
+
this._scrollbackRowEls = [];
|
|
282
|
+
}
|
|
283
|
+
_resolveFirstLiveGridRow() {
|
|
284
|
+
return Array.from(this.container.children).find((child) => child instanceof HTMLElement && child.classList.contains("term-row") && !child.classList.contains("term-scrollback-row")) ?? null;
|
|
285
|
+
}
|
|
286
|
+
_insertScrollbackFragment(fragment) {
|
|
287
|
+
const firstGridRow = this._resolveFirstLiveGridRow();
|
|
288
|
+
if (firstGridRow instanceof HTMLElement) {
|
|
289
|
+
this.container.insertBefore(fragment, firstGridRow);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
this.container.appendChild(fragment);
|
|
293
|
+
}
|
|
294
|
+
setup(cols, rows) {
|
|
295
|
+
this.cols = cols;
|
|
296
|
+
this.rows = rows;
|
|
297
|
+
this.container.innerHTML = "";
|
|
298
|
+
this.rowEls = [];
|
|
299
|
+
this.prevRowBg = [];
|
|
300
|
+
this._scrollbackRowEls = [];
|
|
301
|
+
this._renderedScrollbackCount = 0;
|
|
302
|
+
this.prevCursorVisible = false;
|
|
303
|
+
this.prevCursorRow = -1;
|
|
304
|
+
this.prevCursorCol = -1;
|
|
305
|
+
this._altScreenVisibleRowSnapshot = [];
|
|
306
|
+
this._altScreenMeaningfulScrollbackCount = 0;
|
|
307
|
+
this._lastAltScreenState = false;
|
|
308
|
+
this._restoreSnapshotCount = 0;
|
|
309
|
+
const fragment = document.createDocumentFragment();
|
|
310
|
+
for (let row = 0; row < rows; row += 1) {
|
|
311
|
+
const rowElement = document.createElement("div");
|
|
312
|
+
rowElement.className = "term-row";
|
|
313
|
+
fragment.appendChild(rowElement);
|
|
314
|
+
this.rowEls.push(rowElement);
|
|
315
|
+
}
|
|
316
|
+
this.container.appendChild(fragment);
|
|
317
|
+
}
|
|
318
|
+
_buildRowContent(rowEl, getCell, getOverride, lineLen, cursorCol, rowIndex) {
|
|
319
|
+
if (rowIndex >= 0) rowEl.className = "term-row";
|
|
320
|
+
rowEl.textContent = "";
|
|
321
|
+
let runStart = 0;
|
|
322
|
+
let runStyle = "";
|
|
323
|
+
let runText = "";
|
|
324
|
+
const flushRun = (endCol) => {
|
|
325
|
+
if (!runText) return;
|
|
326
|
+
if (cursorCol >= runStart && cursorCol < endCol) {
|
|
327
|
+
const offset = cursorCol - runStart;
|
|
328
|
+
const before = runText.slice(0, offset);
|
|
329
|
+
const cursorChar = runText[offset];
|
|
330
|
+
const after = runText.slice(offset + 1);
|
|
331
|
+
if (before) appendRun(rowEl, before, runStyle);
|
|
332
|
+
appendRun(rowEl, cursorChar, runStyle, "term-cursor");
|
|
333
|
+
if (after) appendRun(rowEl, after, runStyle);
|
|
334
|
+
} else appendRun(rowEl, runText, runStyle);
|
|
335
|
+
};
|
|
336
|
+
for (let col = 0; col < this.cols; col += 1) {
|
|
337
|
+
const cell = getCell(col);
|
|
338
|
+
const inBounds = col < lineLen;
|
|
339
|
+
const codePoint = inBounds ? cell.char : 0;
|
|
340
|
+
const override = getOverride(col);
|
|
341
|
+
if (override?.hidden === true) {
|
|
342
|
+
flushRun(col);
|
|
343
|
+
runStart = col + 1;
|
|
344
|
+
runStyle = "";
|
|
345
|
+
runText = "";
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
if (inBounds && isWideCodePoint(codePoint)) {
|
|
349
|
+
const consumedColumns = (col + 1 < lineLen && isWideContinuationCell(getCell(col + 1)) ? getCell(col + 1) : null) ? 2 : 1;
|
|
350
|
+
const style = buildCellStyle(cell.fg, cell.bg, cell.flags, override);
|
|
351
|
+
const cursorOnWideCell = cursorCol === col || consumedColumns === 2 && cursorCol === col + 1 || cursorCol === col + 1 && getOverride(cursorCol)?.hidden === true;
|
|
352
|
+
flushRun(col);
|
|
353
|
+
appendWideCell(rowEl, String.fromCodePoint(codePoint), style, cursorOnWideCell);
|
|
354
|
+
runStart = col + consumedColumns;
|
|
355
|
+
runStyle = "";
|
|
356
|
+
runText = "";
|
|
357
|
+
col += consumedColumns - 1;
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
if (inBounds && codePoint >= 9600 && codePoint <= 9631) {
|
|
361
|
+
flushRun(col);
|
|
362
|
+
const colors = resolveColors(cell.fg, cell.bg, cell.flags, override);
|
|
363
|
+
const span = document.createElement("span");
|
|
364
|
+
span.className = col === cursorCol ? "term-block term-cursor" : "term-block";
|
|
365
|
+
span.style.background = getBlockBackground(codePoint, colors.fg, colors.bg);
|
|
366
|
+
if (cell.flags & FLAG_DIM) span.style.opacity = "0.5";
|
|
367
|
+
rowEl.appendChild(span);
|
|
368
|
+
runStart = col + 1;
|
|
369
|
+
runStyle = "";
|
|
370
|
+
runText = "";
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
const character = inBounds && codePoint >= 32 ? String.fromCodePoint(codePoint) : " ";
|
|
374
|
+
const style = inBounds ? buildCellStyle(cell.fg, cell.bg, cell.flags, override) : "";
|
|
375
|
+
if (style !== runStyle) {
|
|
376
|
+
flushRun(col);
|
|
377
|
+
runStart = col;
|
|
378
|
+
runStyle = style;
|
|
379
|
+
runText = character;
|
|
380
|
+
} else runText += character;
|
|
381
|
+
}
|
|
382
|
+
flushRun(this.cols);
|
|
383
|
+
if (rowIndex >= 0) {
|
|
384
|
+
if (this.prevRowBg[rowIndex] !== "") {
|
|
385
|
+
rowEl.style.background = "";
|
|
386
|
+
rowEl.style.boxShadow = "";
|
|
387
|
+
this.prevRowBg[rowIndex] = "";
|
|
388
|
+
}
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
rowEl.style.background = "";
|
|
392
|
+
rowEl.style.boxShadow = "";
|
|
393
|
+
}
|
|
394
|
+
_buildScrollbackRowEl(bridge, offset) {
|
|
395
|
+
const rowElement = document.createElement("div");
|
|
396
|
+
rowElement.className = "term-row term-scrollback-row";
|
|
397
|
+
const lineLength = bridge.getScrollbackLineLen(offset);
|
|
398
|
+
this._buildRowContent(rowElement, (col) => bridge.getScrollbackCell(offset, col), (col) => this.styleTracker.getScrollbackOverride(offset, col), lineLength, -1, -1);
|
|
399
|
+
return rowElement;
|
|
400
|
+
}
|
|
401
|
+
_captureVisibleRowSnapshot() {
|
|
402
|
+
this._altScreenMeaningfulScrollbackCount = this._scrollbackRowEls.reduce((count, rowElement) => count + (normalizeRowText(rowElement.textContent ?? "").length > 0 ? 1 : 0), 0);
|
|
403
|
+
this._altScreenVisibleRowSnapshot = this.rowEls.map((rowElement) => ({
|
|
404
|
+
element: rowElement.cloneNode(true),
|
|
405
|
+
text: normalizeRowText(rowElement.textContent ?? "")
|
|
406
|
+
}));
|
|
407
|
+
}
|
|
408
|
+
_clearAltScreenRestoreSnapshot() {
|
|
409
|
+
this._altScreenVisibleRowSnapshot = [];
|
|
410
|
+
this._altScreenMeaningfulScrollbackCount = 0;
|
|
411
|
+
}
|
|
412
|
+
_readGridRowText(bridge, row) {
|
|
413
|
+
return serializeRowText((col) => bridge.getCell(row, col), this.cols);
|
|
414
|
+
}
|
|
415
|
+
getVisibleRowTexts(bridge, { trimPadding = true } = {}) {
|
|
416
|
+
const rows = [];
|
|
417
|
+
for (let row = 0; row < this.rows; row += 1) rows.push(this._readGridRowText(bridge, row));
|
|
418
|
+
if (!trimPadding) return rows;
|
|
419
|
+
let start = 0;
|
|
420
|
+
let end = rows.length;
|
|
421
|
+
while (start < end && rows[start] === "") start += 1;
|
|
422
|
+
while (end > start && rows[end - 1] === "") end -= 1;
|
|
423
|
+
return rows.slice(start, end);
|
|
424
|
+
}
|
|
425
|
+
_readScrollbackRowText(bridge, offset) {
|
|
426
|
+
return serializeRowText((col) => bridge.getScrollbackCell(offset, col), bridge.getScrollbackLineLen(offset));
|
|
427
|
+
}
|
|
428
|
+
_readLiveTranscriptRows(bridge, currentVisibleRows) {
|
|
429
|
+
const liveRows = [];
|
|
430
|
+
for (let offset = bridge.getScrollbackCount() - 1; offset >= 0; offset -= 1) {
|
|
431
|
+
const rowText = this._readScrollbackRowText(bridge, offset);
|
|
432
|
+
if (rowText.length > 0) liveRows.push(rowText);
|
|
433
|
+
}
|
|
434
|
+
for (const rowText of currentVisibleRows) if (rowText.length > 0) liveRows.push(rowText);
|
|
435
|
+
return liveRows;
|
|
436
|
+
}
|
|
437
|
+
_liveTranscriptContainsSnapshots(bridge, snapshots, currentVisibleRows) {
|
|
438
|
+
if (snapshots.length === 0) return true;
|
|
439
|
+
return findRowSequence(this._readLiveTranscriptRows(bridge, currentVisibleRows), snapshots.map((snapshot) => snapshot.text), this._altScreenMeaningfulScrollbackCount) !== -1;
|
|
440
|
+
}
|
|
441
|
+
_getRestoredScrollbackSnapshots(bridge) {
|
|
442
|
+
if (bridge.usingAltScreen() || this._altScreenVisibleRowSnapshot.length === 0) return [];
|
|
443
|
+
const meaningfulSnapshotRows = this._altScreenVisibleRowSnapshot.filter((row) => row.text.length > 0);
|
|
444
|
+
if (meaningfulSnapshotRows.length === 0) {
|
|
445
|
+
this._clearAltScreenRestoreSnapshot();
|
|
446
|
+
return [];
|
|
447
|
+
}
|
|
448
|
+
const currentVisibleRows = [];
|
|
449
|
+
for (let row = 0; row < this.rows; row += 1) currentVisibleRows.push(this._readGridRowText(bridge, row));
|
|
450
|
+
let { missingPrefixCount, overlapCount } = resolveSnapshotPrefixMatch(this._altScreenVisibleRowSnapshot, currentVisibleRows);
|
|
451
|
+
if (missingPrefixCount <= 0) if (overlapCount === 0 && this._lastAltScreenState) missingPrefixCount = meaningfulSnapshotRows.length;
|
|
452
|
+
else if (overlapCount === 0 && this._restoreSnapshotCount > 0) missingPrefixCount = Math.min(this._restoreSnapshotCount, meaningfulSnapshotRows.length);
|
|
453
|
+
else {
|
|
454
|
+
this._clearAltScreenRestoreSnapshot();
|
|
455
|
+
return [];
|
|
456
|
+
}
|
|
457
|
+
const restoredSnapshots = meaningfulSnapshotRows.slice(0, missingPrefixCount);
|
|
458
|
+
if (this._liveTranscriptContainsSnapshots(bridge, restoredSnapshots, currentVisibleRows)) {
|
|
459
|
+
this._clearAltScreenRestoreSnapshot();
|
|
460
|
+
return [];
|
|
461
|
+
}
|
|
462
|
+
return restoredSnapshots;
|
|
463
|
+
}
|
|
464
|
+
_rebuildScrollback(bridge, restoredSnapshots) {
|
|
465
|
+
for (const rowElement of this._scrollbackRowEls) rowElement.remove();
|
|
466
|
+
this._scrollbackRowEls = [];
|
|
467
|
+
if (bridge.usingAltScreen()) {
|
|
468
|
+
this._renderedScrollbackCount = 0;
|
|
469
|
+
this._restoreSnapshotCount = 0;
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
const scrollbackCount = bridge.getScrollbackCount();
|
|
473
|
+
const rowElements = [];
|
|
474
|
+
for (let offset = scrollbackCount - 1; offset >= 0; offset -= 1) rowElements.push(this._buildScrollbackRowEl(bridge, offset));
|
|
475
|
+
if (restoredSnapshots.length > 0) {
|
|
476
|
+
const restoredRowElements = restoredSnapshots.map((snapshot) => cloneSnapshotRow(snapshot));
|
|
477
|
+
const preservedRowCount = Math.max(0, rowElements.length - restoredRowElements.length);
|
|
478
|
+
rowElements.splice(preservedRowCount, rowElements.length - preservedRowCount, ...restoredRowElements);
|
|
479
|
+
}
|
|
480
|
+
const fragment = document.createDocumentFragment();
|
|
481
|
+
for (const rowElement of rowElements) fragment.appendChild(rowElement);
|
|
482
|
+
this._insertScrollbackFragment(fragment);
|
|
483
|
+
this._scrollbackRowEls = rowElements;
|
|
484
|
+
this._renderedScrollbackCount = scrollbackCount;
|
|
485
|
+
this._restoreSnapshotCount = restoredSnapshots.length;
|
|
486
|
+
}
|
|
487
|
+
syncScrollback(bridge, restoredSnapshots = []) {
|
|
488
|
+
const scrollbackCount = bridge.usingAltScreen() ? 0 : bridge.getScrollbackCount();
|
|
489
|
+
if (bridge.usingAltScreen() !== this._lastAltScreenState || restoredSnapshots.length > 0 || this._restoreSnapshotCount > 0) {
|
|
490
|
+
this._rebuildScrollback(bridge, restoredSnapshots);
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
if (scrollbackCount === this._renderedScrollbackCount) return;
|
|
494
|
+
if (scrollbackCount > this._renderedScrollbackCount) {
|
|
495
|
+
const newCount = scrollbackCount - this._renderedScrollbackCount;
|
|
496
|
+
const fragment = document.createDocumentFragment();
|
|
497
|
+
for (let offset = newCount - 1; offset >= 0; offset -= 1) {
|
|
498
|
+
const rowElement = this._buildScrollbackRowEl(bridge, offset);
|
|
499
|
+
fragment.appendChild(rowElement);
|
|
500
|
+
this._scrollbackRowEls.push(rowElement);
|
|
501
|
+
}
|
|
502
|
+
this._insertScrollbackFragment(fragment);
|
|
503
|
+
} else {
|
|
504
|
+
const removeCount = this._renderedScrollbackCount - scrollbackCount;
|
|
505
|
+
for (let index = 0; index < removeCount; index += 1) this._scrollbackRowEls.shift()?.remove();
|
|
506
|
+
}
|
|
507
|
+
this._renderedScrollbackCount = scrollbackCount;
|
|
508
|
+
}
|
|
509
|
+
render(bridge, options = {}) {
|
|
510
|
+
const rows = bridge.getRows();
|
|
511
|
+
const cols = bridge.getCols();
|
|
512
|
+
const altScreenActive = bridge.usingAltScreen();
|
|
513
|
+
const forceRender = options.force === true;
|
|
514
|
+
let resized = false;
|
|
515
|
+
if (rows !== this.rows || cols !== this.cols) {
|
|
516
|
+
this.setup(cols, rows);
|
|
517
|
+
resized = true;
|
|
518
|
+
}
|
|
519
|
+
if (altScreenActive && !this._lastAltScreenState) this._captureVisibleRowSnapshot();
|
|
520
|
+
const restoredSnapshots = this._getRestoredScrollbackSnapshots(bridge);
|
|
521
|
+
this.syncScrollback(bridge, restoredSnapshots);
|
|
522
|
+
const cursor = bridge.getCursor();
|
|
523
|
+
const cursorVisible = cursor.visible;
|
|
524
|
+
const needsCursorUpdate = cursor.row !== this.prevCursorRow || cursor.col !== this.prevCursorCol || cursorVisible !== this.prevCursorVisible;
|
|
525
|
+
for (let row = 0; row < this.rows; row += 1) {
|
|
526
|
+
const isDirty = forceRender || resized || bridge.isDirtyRow(row);
|
|
527
|
+
const hadCursor = row === this.prevCursorRow && (needsCursorUpdate || this.prevCursorVisible);
|
|
528
|
+
const hasCursor = row === cursor.row;
|
|
529
|
+
if (isDirty || hadCursor || hasCursor && needsCursorUpdate) {
|
|
530
|
+
const cursorCol = hasCursor && cursorVisible ? cursor.col : -1;
|
|
531
|
+
this._buildRowContent(this.rowEls[row], (col) => bridge.getCell(row, col), (col) => this.styleTracker.getGridOverride(row, col), this.cols, cursorCol, row);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
this.prevCursorRow = cursor.row;
|
|
535
|
+
this.prevCursorCol = cursor.col;
|
|
536
|
+
this.prevCursorVisible = cursorVisible;
|
|
537
|
+
if (this.prevContainerBg !== "") {
|
|
538
|
+
this.container.style.background = "";
|
|
539
|
+
this.prevContainerBg = "";
|
|
540
|
+
}
|
|
541
|
+
bridge.clearDirty();
|
|
542
|
+
this._lastAltScreenState = altScreenActive;
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
//#endregion
|
|
546
|
+
export { BrowserTerminalRenderer };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
//#region src/frontend/cell-width.ts
|
|
2
|
+
function isWideCodePoint(codePoint) {
|
|
3
|
+
return codePoint >= 4352 && (codePoint <= 4447 || codePoint === 9001 || codePoint === 9002 || codePoint >= 11904 && codePoint <= 42191 && codePoint !== 12351 || codePoint >= 44032 && codePoint <= 55203 || codePoint >= 63744 && codePoint <= 64255 || codePoint >= 65040 && codePoint <= 65049 || codePoint >= 65072 && codePoint <= 65135 || codePoint >= 65280 && codePoint <= 65376 || codePoint >= 65504 && codePoint <= 65510 || codePoint >= 127744 && codePoint <= 128591 || codePoint >= 129280 && codePoint <= 129535 || codePoint >= 131072 && codePoint <= 262141);
|
|
4
|
+
}
|
|
5
|
+
function cellWidthForCodePoint(codePoint) {
|
|
6
|
+
return isWideCodePoint(codePoint) ? 2 : 1;
|
|
7
|
+
}
|
|
8
|
+
//#endregion
|
|
9
|
+
export { cellWidthForCodePoint, isWideCodePoint };
|