first commit
This commit is contained in:
159
apps/miniprogram/pages/terminal/terminalSnapshotCodec.js
Normal file
159
apps/miniprogram/pages/terminal/terminalSnapshotCodec.js
Normal file
@@ -0,0 +1,159 @@
|
||||
/* global module, require */
|
||||
|
||||
const {
|
||||
buildLineCellRenderRuns,
|
||||
createBlankCell,
|
||||
createContinuationCell,
|
||||
createTerminalCell,
|
||||
measureCharDisplayColumns
|
||||
} = require("./terminalCursorModel.js");
|
||||
|
||||
/**
|
||||
* 终端快照样式做最小化存储:
|
||||
* 1. 仅保留当前渲染真正需要的 fg/bg/bold/underline;
|
||||
* 2. 同一份样式进入 style table 去重,line runs 只保留索引;
|
||||
* 3. 这样比直接存整行 cell 更省空间,也避免恢复时回退成纯文本。
|
||||
*/
|
||||
function normalizeSnapshotStyle(style) {
|
||||
const source = style && typeof style === "object" ? style : null;
|
||||
if (!source) return null;
|
||||
const fg = String(source.fg || "").trim();
|
||||
const bg = String(source.bg || "").trim();
|
||||
const bold = source.bold === true;
|
||||
const underline = source.underline === true;
|
||||
if (!fg && !bg && !bold && !underline) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
fg,
|
||||
bg,
|
||||
bold,
|
||||
underline
|
||||
};
|
||||
}
|
||||
|
||||
function buildSnapshotStyleSignature(style) {
|
||||
const normalized = normalizeSnapshotStyle(style);
|
||||
if (!normalized) return "";
|
||||
return `${normalized.fg || ""}|${normalized.bg || ""}|${normalized.bold ? 1 : 0}|${normalized.underline ? 1 : 0}`;
|
||||
}
|
||||
|
||||
function cloneSnapshotStyle(style) {
|
||||
const normalized = normalizeSnapshotStyle(style);
|
||||
return normalized ? { ...normalized } : null;
|
||||
}
|
||||
|
||||
function measureTextDisplayColumns(text) {
|
||||
const value = String(text || "");
|
||||
if (!value) return 0;
|
||||
let columns = 0;
|
||||
for (let index = 0; index < value.length; ) {
|
||||
const codePoint = value.codePointAt(index);
|
||||
if (!Number.isFinite(codePoint)) break;
|
||||
const ch = String.fromCodePoint(codePoint);
|
||||
index += ch.length;
|
||||
const width = measureCharDisplayColumns(ch);
|
||||
if (width > 0) {
|
||||
columns += width;
|
||||
}
|
||||
}
|
||||
return columns;
|
||||
}
|
||||
|
||||
function appendStyledTextCells(cells, text, style) {
|
||||
const value = String(text || "");
|
||||
if (!value) return;
|
||||
for (let index = 0; index < value.length; ) {
|
||||
const codePoint = value.codePointAt(index);
|
||||
if (!Number.isFinite(codePoint)) break;
|
||||
const ch = String.fromCodePoint(codePoint);
|
||||
index += ch.length;
|
||||
const width = measureCharDisplayColumns(ch);
|
||||
if (width <= 0) {
|
||||
for (let ownerIndex = cells.length - 1; ownerIndex >= 0; ownerIndex -= 1) {
|
||||
if (cells[ownerIndex] && !cells[ownerIndex].continuation) {
|
||||
cells[ownerIndex].text = `${cells[ownerIndex].text || ""}${ch}`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
cells.push(createTerminalCell(ch, cloneSnapshotStyle(style), width));
|
||||
for (let rest = width - 1; rest > 0; rest -= 1) {
|
||||
cells.push(createContinuationCell(cloneSnapshotStyle(style)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function serializeTerminalSnapshotRows(rowsInput) {
|
||||
const rows = Array.isArray(rowsInput) ? rowsInput : [];
|
||||
const styleTable = [];
|
||||
const styleIndexBySignature = new Map();
|
||||
const styledLines = rows.map((lineCells) => {
|
||||
const runs = buildLineCellRenderRuns(Array.isArray(lineCells) ? lineCells : []);
|
||||
return runs.map((run) => {
|
||||
const entry = {};
|
||||
const text = String((run && run.text) || "");
|
||||
const columns = Math.max(0, Math.round(Number(run && run.columns) || 0));
|
||||
if (text) {
|
||||
entry.t = text;
|
||||
}
|
||||
if (columns > 0) {
|
||||
entry.c = columns;
|
||||
}
|
||||
if (run && run.fixed) {
|
||||
entry.f = 1;
|
||||
}
|
||||
const styleSignature = buildSnapshotStyleSignature(run && run.style);
|
||||
if (styleSignature) {
|
||||
let styleIndex = styleIndexBySignature.get(styleSignature);
|
||||
if (!Number.isInteger(styleIndex)) {
|
||||
styleIndex = styleTable.length;
|
||||
styleIndexBySignature.set(styleSignature, styleIndex);
|
||||
styleTable.push(cloneSnapshotStyle(run.style));
|
||||
}
|
||||
entry.s = styleIndex;
|
||||
}
|
||||
return entry;
|
||||
});
|
||||
});
|
||||
return {
|
||||
styleTable,
|
||||
styledLines
|
||||
};
|
||||
}
|
||||
|
||||
function deserializeTerminalSnapshotRows(linesInput, styleTableInput) {
|
||||
const styleTable = Array.isArray(styleTableInput) ? styleTableInput.map((style) => cloneSnapshotStyle(style)) : [];
|
||||
const lines = Array.isArray(linesInput) ? linesInput : [];
|
||||
return lines.map((lineRuns) => {
|
||||
const cells = [];
|
||||
const runs = Array.isArray(lineRuns) ? lineRuns : [];
|
||||
runs.forEach((run) => {
|
||||
const source = run && typeof run === "object" ? run : {};
|
||||
const text = String(source.t || "");
|
||||
const columns = Math.max(
|
||||
0,
|
||||
Math.round(Number(source.c !== undefined ? source.c : measureTextDisplayColumns(text)) || 0)
|
||||
);
|
||||
const styleIndex = Number(source.s);
|
||||
const style =
|
||||
Number.isInteger(styleIndex) && styleIndex >= 0 && styleIndex < styleTable.length
|
||||
? styleTable[styleIndex]
|
||||
: null;
|
||||
if (!text) {
|
||||
for (let index = 0; index < columns; index += 1) {
|
||||
cells.push(createBlankCell(cloneSnapshotStyle(style)));
|
||||
}
|
||||
return;
|
||||
}
|
||||
appendStyledTextCells(cells, text, style);
|
||||
});
|
||||
return cells;
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
deserializeTerminalSnapshotRows,
|
||||
serializeTerminalSnapshotRows
|
||||
};
|
||||
Reference in New Issue
Block a user