first commit

This commit is contained in:
douboer
2026-03-21 18:57:10 +08:00
commit c49aa1a5e9
570 changed files with 107167 additions and 0 deletions

View File

@@ -0,0 +1,558 @@
/* global module, require */
/**
* 轻量 VT 输入处理层:
* 1. 承接“模式位切换 / 查询响应 / 软重置”这类协议状态逻辑;
* 2. 不直接持有 buffer 数据结构,只通过上下文回调读写运行态;
* 3. 先把最容易继续膨胀的协议分支从 `terminalBufferState` 中拆出,为后续继续下沉 CSI/ESC 处理铺路。
*/
const { DEFAULT_TERMINAL_MODES } = require("./terminalBufferSet.js");
function createTerminalVtInputHandler(options) {
const source = options && typeof options === "object" ? options : {};
const responses = Array.isArray(source.responses) ? source.responses : [];
const runtimeColors = source.runtimeColors && typeof source.runtimeColors === "object" ? source.runtimeColors : {};
const runtimeState = source.runtimeState && typeof source.runtimeState === "object" ? source.runtimeState : {};
const bufferCols = Math.max(1, Math.round(Number(source.bufferCols) || 0));
const bufferRows = Math.max(1, Math.round(Number(source.bufferRows) || 0));
const cloneAnsiState =
typeof source.cloneAnsiState === "function"
? source.cloneAnsiState
: (state) => ({ ...(state && typeof state === "object" ? state : {}) });
const toOscRgbString =
typeof source.toOscRgbString === "function" ? source.toOscRgbString : () => "";
const getActiveBuffer =
typeof source.getActiveBuffer === "function" ? source.getActiveBuffer : () => null;
const setCursorRow = typeof source.setCursorRow === "function" ? source.setCursorRow : () => {};
const getCursorCol = typeof source.getCursorCol === "function" ? source.getCursorCol : () => 0;
const setCursorCol = typeof source.setCursorCol === "function" ? source.setCursorCol : () => {};
const getScrollTop = typeof source.getScrollTop === "function" ? source.getScrollTop : () => 0;
const setScrollTop = typeof source.setScrollTop === "function" ? source.setScrollTop : () => {};
const getScrollBottom =
typeof source.getScrollBottom === "function" ? source.getScrollBottom : () => Math.max(0, bufferRows - 1);
const setScrollBottom =
typeof source.setScrollBottom === "function" ? source.setScrollBottom : () => {};
const setAnsiState = typeof source.setAnsiState === "function" ? source.setAnsiState : () => {};
const moveCursorUp = typeof source.moveCursorUp === "function" ? source.moveCursorUp : () => {};
const moveCursorDown = typeof source.moveCursorDown === "function" ? source.moveCursorDown : () => {};
const moveCursorRight =
typeof source.moveCursorRight === "function" ? source.moveCursorRight : () => {};
const moveCursorLeft = typeof source.moveCursorLeft === "function" ? source.moveCursorLeft : () => {};
const moveCursorNextLine =
typeof source.moveCursorNextLine === "function" ? source.moveCursorNextLine : () => {};
const moveCursorPreviousLine =
typeof source.moveCursorPreviousLine === "function" ? source.moveCursorPreviousLine : () => {};
const setCursorColumn1 =
typeof source.setCursorColumn1 === "function" ? source.setCursorColumn1 : () => {};
const setCursorRow1 = typeof source.setCursorRow1 === "function" ? source.setCursorRow1 : () => {};
const setCursorPosition1 =
typeof source.setCursorPosition1 === "function" ? source.setCursorPosition1 : () => {};
const clearDisplayByMode =
typeof source.clearDisplayByMode === "function" ? source.clearDisplayByMode : () => {};
const clearLineByMode =
typeof source.clearLineByMode === "function" ? source.clearLineByMode : () => {};
const eraseChars = typeof source.eraseChars === "function" ? source.eraseChars : () => {};
const insertChars = typeof source.insertChars === "function" ? source.insertChars : () => {};
const deleteChars = typeof source.deleteChars === "function" ? source.deleteChars : () => {};
const insertLines = typeof source.insertLines === "function" ? source.insertLines : () => {};
const deleteLines = typeof source.deleteLines === "function" ? source.deleteLines : () => {};
const scrollRegionUp =
typeof source.scrollRegionUp === "function" ? source.scrollRegionUp : () => {};
const scrollRegionDown =
typeof source.scrollRegionDown === "function" ? source.scrollRegionDown : () => {};
const setScrollRegion =
typeof source.setScrollRegion === "function" ? source.setScrollRegion : () => {};
const resetCursorForOriginMode =
typeof source.resetCursorForOriginMode === "function" ? source.resetCursorForOriginMode : null;
const indexDown = typeof source.indexDown === "function" ? source.indexDown : () => {};
const nextLine = typeof source.nextLine === "function" ? source.nextLine : () => {};
const reverseIndex =
typeof source.reverseIndex === "function" ? source.reverseIndex : () => {};
const getScreenCursorRow =
typeof source.getScreenCursorRow === "function" ? source.getScreenCursorRow : () => 0;
const saveCurrentCursor =
typeof source.saveCurrentCursor === "function" ? source.saveCurrentCursor : () => {};
const restoreCurrentCursor =
typeof source.restoreCurrentCursor === "function" ? source.restoreCurrentCursor : () => {};
const switchActiveBuffer =
typeof source.switchActiveBuffer === "function" ? source.switchActiveBuffer : () => {};
const ansiResetState =
source.ansiResetState && typeof source.ansiResetState === "object" ? source.ansiResetState : {};
function pushDeviceStatusResponse(privateMarker, code) {
if (code === 5) {
responses.push(`${privateMarker === "?" ? "\u001b[?" : "\u001b["}0n`);
return;
}
if (code === 6) {
const row = getScreenCursorRow() + 1;
const col = Math.min(bufferCols, getCursorCol()) + 1;
responses.push(privateMarker === "?" ? `\u001b[?${row};${col}R` : `\u001b[${row};${col}R`);
}
}
/**
* 设备属性查询只回报当前实现真实支持的“最小可用口径”,
* 避免把不存在的终端特性伪装成已支持。
*/
function pushDeviceAttributesResponse(privateMarker, code) {
if (code > 0) {
return;
}
if (privateMarker === ">") {
responses.push("\u001b[>0;276;0c");
return;
}
responses.push("\u001b[?1;2c");
}
function pushOscColorReport(ident) {
let color = "";
if (ident === "10") {
color = runtimeColors.defaultForeground;
} else if (ident === "11") {
color = runtimeColors.defaultBackground;
} else if (ident === "12") {
color = runtimeColors.defaultCursor;
}
const rgb = toOscRgbString(color);
if (!rgb) {
return;
}
responses.push(`\u001b]${ident};${rgb}\u001b\\`);
}
/**
* OSC 10/11/12 允许把多个查询串在一条指令里,这里只对当前已实现的颜色槽位做查询响应。
*/
function handleOscSequence(ident, data) {
const base = Number(ident);
if (![10, 11, 12].includes(base)) {
return;
}
const slots = String(data || "").split(";");
for (let offset = 0; offset < slots.length; offset += 1) {
const slotIdent = String(base + offset);
if (!["10", "11", "12"].includes(slotIdent)) {
break;
}
if (slots[offset] === "?") {
pushOscColorReport(slotIdent);
}
}
}
function pushModeStatusResponse(privateMarker, mode, status) {
const prefix = privateMarker === "?" ? "?" : "";
responses.push(`\u001b[${prefix}${mode};${status}$y`);
}
/**
* 这里只回报当前运行态里真实维护的模式位。
* 未实现模式统一返回 0避免协议层把“未知能力”冒充成“已支持”。
*/
function resolveModeStatus(privateMarker, value) {
const mode = Math.round(Number(value) || 0);
if (!mode) return 0;
if (privateMarker !== "?") {
if (mode === 4) {
return runtimeState.modes.insertMode ? 1 : 2;
}
return 0;
}
if (mode === 1) return runtimeState.modes.applicationCursorKeys ? 1 : 2;
if (mode === 6) return runtimeState.modes.originMode ? 1 : 2;
if (mode === 7) return runtimeState.modes.wraparound ? 1 : 2;
if (mode === 25) return runtimeState.modes.cursorHidden ? 2 : 1;
if (mode === 45) return runtimeState.modes.reverseWraparound ? 1 : 2;
if (mode === 66) return runtimeState.modes.applicationKeypad ? 1 : 2;
if (mode === 47 || mode === 1047 || mode === 1049) {
const activeBuffer = getActiveBuffer();
return activeBuffer && activeBuffer.isAlt ? 1 : 2;
}
if (mode === 1048) return 1;
if (mode === 1004) return runtimeState.modes.sendFocus ? 1 : 2;
if (mode === 2004) return runtimeState.modes.bracketedPasteMode ? 1 : 2;
return 0;
}
function pushModeReport(privateMarker, value) {
const mode = Math.round(Number(value) || 0);
if (!mode) return;
pushModeStatusResponse(privateMarker, mode, resolveModeStatus(privateMarker, mode));
}
function pushStatusStringResponse(payload) {
const value = String(payload || "");
if (value === "m") {
responses.push("\u001bP1$r0m\u001b\\");
return;
}
if (value === "r") {
responses.push(`\u001bP1$r${getScrollTop() + 1};${getScrollBottom() + 1}r\u001b\\`);
return;
}
if (value === " q") {
responses.push("\u001bP1$r2 q\u001b\\");
return;
}
if (value === '"q') {
responses.push('\u001bP1$r0"q\u001b\\');
return;
}
if (value === '"p') {
responses.push('\u001bP1$r61;1"p\u001b\\');
return;
}
responses.push("\u001bP0$r\u001b\\");
}
/**
* DECSTR 只做软重置,不清屏、不搬动当前 cursor。
* 这部分逻辑必须保持和现有主链路一致,避免复杂 TUI 在软重置后发生可见跳变。
*/
function softResetTerminal() {
runtimeState.modes = { ...DEFAULT_TERMINAL_MODES };
setAnsiState(cloneAnsiState(ansiResetState));
setScrollTop(0);
setScrollBottom(Math.max(0, bufferRows - 1));
const activeBuffer = getActiveBuffer();
if (activeBuffer) {
activeBuffer.savedCursorRow = 0;
activeBuffer.savedCursorCol = 0;
activeBuffer.savedAnsiState = cloneAnsiState(ansiResetState);
}
}
function handlePrivateMode(enabled, value) {
const mode = Math.round(Number(value) || 0);
if (!mode) return;
if (mode === 1) {
runtimeState.modes.applicationCursorKeys = enabled;
return;
}
if (mode === 6) {
runtimeState.modes.originMode = enabled;
if (resetCursorForOriginMode) {
resetCursorForOriginMode(enabled);
return;
}
const activeBuffer = getActiveBuffer();
setCursorRow(enabled && activeBuffer && activeBuffer.isAlt ? getScrollTop() : 0);
setCursorCol(0);
return;
}
if (mode === 45) {
runtimeState.modes.reverseWraparound = enabled;
return;
}
if (mode === 66) {
runtimeState.modes.applicationKeypad = enabled;
return;
}
if (mode === 7) {
runtimeState.modes.wraparound = enabled;
return;
}
if (mode === 25) {
runtimeState.modes.cursorHidden = !enabled;
return;
}
if (mode === 47 || mode === 1047) {
switchActiveBuffer(enabled ? "alt" : "normal", enabled);
return;
}
if (mode === 1048) {
if (enabled) {
saveCurrentCursor();
} else {
restoreCurrentCursor();
}
return;
}
if (mode === 1049) {
if (enabled) {
saveCurrentCursor();
switchActiveBuffer("alt", true);
} else {
switchActiveBuffer("normal", false);
restoreCurrentCursor();
}
return;
}
if (mode === 2004) {
runtimeState.modes.bracketedPasteMode = enabled;
return;
}
if (mode === 1004) {
runtimeState.modes.sendFocus = enabled;
}
}
function handleAnsiMode(enabled, value) {
const mode = Math.round(Number(value) || 0);
if (!mode) return;
if (mode === 4) {
runtimeState.modes.insertMode = enabled;
}
}
function resolveCsiNumber(values, index, fallback) {
const value = values && Number(values[index]);
if (!Number.isFinite(value)) return fallback;
const normalized = Math.round(value);
if (normalized < 0) return 0;
return normalized;
}
/**
* 这里只下沉“CSI -> 光标动作”的协议解释:
* 1. handler 负责默认参数、1-based 行列语义和 final byte 分派;
* 2. bufferState 仍负责真实行列边界、history/alt buffer 与 origin mode 的具体落点。
*/
function handleCursorControl(final, values) {
const code = String(final || "");
if (code === "A") {
moveCursorUp(Math.max(1, resolveCsiNumber(values, 0, 1)));
return true;
}
if (code === "B") {
moveCursorDown(Math.max(1, resolveCsiNumber(values, 0, 1)));
return true;
}
if (code === "C") {
moveCursorRight(Math.max(1, resolveCsiNumber(values, 0, 1)));
return true;
}
if (code === "D") {
moveCursorLeft(Math.max(1, resolveCsiNumber(values, 0, 1)));
return true;
}
if (code === "E") {
moveCursorNextLine(Math.max(1, resolveCsiNumber(values, 0, 1)));
return true;
}
if (code === "F") {
moveCursorPreviousLine(Math.max(1, resolveCsiNumber(values, 0, 1)));
return true;
}
if (code === "G") {
setCursorColumn1(Math.max(1, resolveCsiNumber(values, 0, 1)));
return true;
}
if (code === "d") {
setCursorRow1(Math.max(1, resolveCsiNumber(values, 0, 1)));
return true;
}
if (code === "H" || code === "f") {
setCursorPosition1(
Math.max(1, resolveCsiNumber(values, 0, 1)),
Math.max(1, resolveCsiNumber(values, 1, 1))
);
return true;
}
return false;
}
/**
* 擦除类 CSI 只在这里解释参数语义:
* 1. `J / K` 走“显示/行清除模式”;
* 2. `X` 走“从当前光标起擦除 N 列”,默认值保持 1。
*/
function handleEraseControl(final, values) {
const code = String(final || "");
if (code === "J") {
clearDisplayByMode(resolveCsiNumber(values, 0, 0));
return true;
}
if (code === "K") {
clearLineByMode(resolveCsiNumber(values, 0, 0));
return true;
}
if (code === "X") {
eraseChars(Math.max(1, resolveCsiNumber(values, 0, 1)));
return true;
}
return false;
}
/**
* 编辑/滚动类 CSI 仍然只解释“协议参数 -> buffer 动作”的映射,
* 真正的 cell 变更、滚动区约束和 viewport/history 处理继续留在 bufferState。
*/
function handleEditControl(final, values) {
const code = String(final || "");
if (code === "@") {
insertChars(Math.max(1, resolveCsiNumber(values, 0, 1)));
return true;
}
if (code === "P") {
deleteChars(Math.max(1, resolveCsiNumber(values, 0, 1)));
return true;
}
if (code === "L") {
insertLines(Math.max(1, resolveCsiNumber(values, 0, 1)));
return true;
}
if (code === "M") {
deleteLines(Math.max(1, resolveCsiNumber(values, 0, 1)));
return true;
}
if (code === "S") {
scrollRegionUp(Math.max(1, resolveCsiNumber(values, 0, 1)));
return true;
}
if (code === "T") {
scrollRegionDown(Math.max(1, resolveCsiNumber(values, 0, 1)));
return true;
}
if (code === "r") {
const top = Math.max(1, resolveCsiNumber(values, 0, 1));
const rawBottom = values && values.length > 1 ? resolveCsiNumber(values, 1, bufferRows) : bufferRows;
const bottom = rawBottom > 0 ? rawBottom : bufferRows;
setScrollRegion(top, bottom);
return true;
}
return false;
}
/**
* 保存/恢复光标这类协议动作目前同时存在于 CSI 与 ESC 两条入口:
* 1. `CSI s / u`
* 2. `ESC 7 / 8`
* 这里统一只做协议语义分派,真实保存内容仍由 bufferState 维护。
*/
function handleCursorSaveRestoreControl(final) {
const code = String(final || "");
if (code === "s" || code === "7") {
saveCurrentCursor();
return true;
}
if (code === "u" || code === "8") {
restoreCurrentCursor();
return true;
}
return false;
}
/**
* 查询类 CSI 统一在这里做协议分派,减少主循环里零散的条件判断:
* 1. `DECRQM``CSI Ps $ p` / `CSI ? Ps $ p`
* 2. `DSR / CPR``CSI n` / `CSI ? n`
* 3. `DA1 / DA2``CSI c` / `CSI > c`
*/
function handleQueryControl(privateMarker, intermediates, final, values) {
const marker = String(privateMarker || "");
const middle = String(intermediates || "");
const code = String(final || "");
if (middle === "$" && code === "p" && (marker === "" || marker === "?")) {
(Array.isArray(values) && values.length > 0 ? values : [0]).forEach((mode) => {
pushModeReport(marker, mode);
});
return true;
}
if (code === "n" && (marker === "" || marker === "?")) {
pushDeviceStatusResponse(marker, resolveCsiNumber(values, 0, 0));
return true;
}
if (code === "c" && (marker === "" || marker === ">")) {
pushDeviceAttributesResponse(marker, resolveCsiNumber(values, 0, 0));
return true;
}
return false;
}
/**
* ESC 入口目前只保留真正的 ESC 协议语义分派:
* 1. `ESC 7 / 8` 保存恢复光标
* 2. `ESC D / E / M` index / next line / reverse index
*/
function handleEscControl(final) {
const code = String(final || "");
if (handleCursorSaveRestoreControl(code)) {
return true;
}
if (code === "D") {
indexDown();
return true;
}
if (code === "E") {
nextLine();
return true;
}
if (code === "M") {
reverseIndex();
return true;
}
return false;
}
/**
* `CSI` 顶层分派只负责“协议入口 -> 已有子处理器”的路由:
* 1. 查询类、模式切换、软重置优先处理,保持和现有协议优先级一致;
* 2. 普通 ANSI 模式与无私有前缀的光标/擦除/编辑动作继续复用已有细分处理器;
* 3. `SGR` 仍留在 bufferState因为它直接作用于当前 ansiState 运行态。
*/
function handleCsiControl(privateMarker, intermediates, final, values) {
const marker = String(privateMarker || "");
const code = String(final || "");
if (handleQueryControl(marker, intermediates, code, values)) {
return true;
}
if (marker === "?") {
if (code === "h" || code === "l") {
const enabled = code === "h";
(Array.isArray(values) && values.length > 0 ? values : [0]).forEach((mode) =>
handlePrivateMode(enabled, mode)
);
return true;
}
return false;
}
if (marker === "!" && code === "p") {
softResetTerminal();
return true;
}
if (!marker && (code === "h" || code === "l")) {
values.forEach((value) => {
handleAnsiMode(code === "h", value);
});
return true;
}
if (marker) {
return false;
}
return (
handleCursorControl(code, values) ||
handleEraseControl(code, values) ||
handleEditControl(code, values) ||
handleCursorSaveRestoreControl(code)
);
}
return {
handleCsiControl,
handleCursorControl,
handleEraseControl,
handleEditControl,
handleCursorSaveRestoreControl,
handleQueryControl,
handleEscControl,
handleAnsiMode,
handleOscSequence,
handlePrivateMode,
pushDeviceAttributesResponse,
pushDeviceStatusResponse,
pushModeReport,
pushStatusStringResponse,
softResetTerminal
};
}
module.exports = {
createTerminalVtInputHandler
};