/* global module, require */ const { cloneTerminalCell } = require("./terminalCursorModel.js"); const TERMINAL_BUFFER_STATE_VERSION = 2; const DEFAULT_ACTIVE_BUFFER = "normal"; const DEFAULT_TERMINAL_MODES = Object.freeze({ applicationCursorKeys: false, applicationKeypad: false, originMode: false, reverseWraparound: false, sendFocus: false, wraparound: true, cursorHidden: false, bracketedPasteMode: false, insertMode: false }); function cloneAnsiState(state) { const source = state && typeof state === "object" ? state : null; return { fg: source && source.fg ? String(source.fg) : "", bg: source && source.bg ? String(source.bg) : "", bold: !!(source && source.bold), underline: !!(source && source.underline) }; } function cloneTerminalBufferRows(rows) { const source = Array.isArray(rows) && rows.length > 0 ? rows : [[]]; return source.map((lineCells) => Array.isArray(lineCells) ? lineCells.map((cell) => cloneTerminalCell(cell)) : [] ); } function clampNonNegativeInteger(value, fallback) { const parsed = Number(value); if (!Number.isFinite(parsed)) return fallback; return Math.max(0, Math.round(parsed)); } function clampBufferRows(value) { return Math.max(1, clampNonNegativeInteger(value, 24)); } function createEmptyRows(count) { const total = Math.max(1, clampNonNegativeInteger(count, 1)); return Array.from({ length: total }, () => []); } function normalizeScreenBuffer(input, options) { const source = input && typeof input === "object" ? input : null; const isAlt = !!(options && options.isAlt); const bufferRows = clampBufferRows(options && options.bufferRows); let cells = cloneTerminalBufferRows(source && source.cells); if (isAlt) { if (cells.length > bufferRows) { cells = cells.slice(0, bufferRows); } while (cells.length < bufferRows) { cells.push([]); } } else if (cells.length === 0) { cells = [[]]; } const cursorRowFallback = 0; const cursorRow = clampNonNegativeInteger(source && source.cursorRow, cursorRowFallback); const cursorCol = clampNonNegativeInteger(source && source.cursorCol, 0); const maxRow = isAlt ? bufferRows - 1 : Math.max(0, Math.max(cells.length - 1, cursorRow)); if (!isAlt) { while (cells.length <= cursorRow) { cells.push([]); } } const normalizedCursorRow = Math.max(0, Math.min(cursorRow, maxRow)); const savedCursorRow = clampNonNegativeInteger(source && source.savedCursorRow, normalizedCursorRow); const normalizedSavedCursorRow = Math.max(0, Math.min(savedCursorRow, maxRow)); const scrollTop = Math.max( 0, Math.min(clampNonNegativeInteger(source && source.scrollTop, 0), bufferRows - 1) ); const scrollBottom = Math.max( scrollTop, Math.min(clampNonNegativeInteger(source && source.scrollBottom, bufferRows - 1), bufferRows - 1) ); return { isAlt, cells, ansiState: cloneAnsiState(source && source.ansiState), cursorRow: normalizedCursorRow, cursorCol, savedCursorRow: normalizedSavedCursorRow, savedCursorCol: clampNonNegativeInteger(source && source.savedCursorCol, cursorCol), savedAnsiState: cloneAnsiState( source && source.savedAnsiState ? source.savedAnsiState : source && source.ansiState ), scrollTop, scrollBottom }; } function normalizeTerminalModes(input) { const source = input && typeof input === "object" ? input : null; return { applicationCursorKeys: source && source.applicationCursorKeys !== undefined ? !!source.applicationCursorKeys : DEFAULT_TERMINAL_MODES.applicationCursorKeys, applicationKeypad: source && source.applicationKeypad !== undefined ? !!source.applicationKeypad : DEFAULT_TERMINAL_MODES.applicationKeypad, originMode: source && source.originMode !== undefined ? !!source.originMode : DEFAULT_TERMINAL_MODES.originMode, reverseWraparound: source && source.reverseWraparound !== undefined ? !!source.reverseWraparound : DEFAULT_TERMINAL_MODES.reverseWraparound, sendFocus: source && source.sendFocus !== undefined ? !!source.sendFocus : DEFAULT_TERMINAL_MODES.sendFocus, wraparound: source && source.wraparound !== undefined ? !!source.wraparound : DEFAULT_TERMINAL_MODES.wraparound, cursorHidden: source && source.cursorHidden !== undefined ? !!source.cursorHidden : DEFAULT_TERMINAL_MODES.cursorHidden, bracketedPasteMode: source && source.bracketedPasteMode !== undefined ? !!source.bracketedPasteMode : DEFAULT_TERMINAL_MODES.bracketedPasteMode, insertMode: source && source.insertMode !== undefined ? !!source.insertMode : DEFAULT_TERMINAL_MODES.insertMode }; } /** * 统一把旧版单缓冲状态和新版双缓冲状态收敛成同一种结构: * 1. `normal/alt` 两套 buffer 永远同形; * 2. 页面层只消费 active buffer 的镜像字段; * 3. 未来补更多 VT 模式时,只在这里扩展,不再把状态分散在页面运行时里。 */ function normalizeTerminalBufferState(input, options, runtimeOptions) { const source = input && typeof input === "object" ? input : null; const bufferRows = clampBufferRows(options && options.bufferRows); const legacyBuffer = source && (!source.version || !source.buffers) && (source.cells || source.cursorRow !== undefined || source.cursorCol !== undefined) ? source : null; const normalSource = source && source.version === TERMINAL_BUFFER_STATE_VERSION && source.buffers ? source.buffers.normal : legacyBuffer; const altSource = source && source.version === TERMINAL_BUFFER_STATE_VERSION && source.buffers ? source.buffers.alt : null; const activeBuffer = source && source.version === TERMINAL_BUFFER_STATE_VERSION && source.activeBuffer === "alt" ? "alt" : DEFAULT_ACTIVE_BUFFER; const normalized = { version: TERMINAL_BUFFER_STATE_VERSION, buffers: { normal: normalizeScreenBuffer(normalSource, { isAlt: false, bufferRows }), alt: normalizeScreenBuffer(altSource, { isAlt: true, bufferRows }) }, activeBuffer, modes: normalizeTerminalModes(source && source.modes ? source.modes : source) }; return syncActiveBufferSnapshot(normalized, options, runtimeOptions); } function cloneTerminalBufferState(input, options, runtimeOptions) { return normalizeTerminalBufferState(input, options, runtimeOptions); } function getActiveTerminalBuffer(state) { const source = state && typeof state === "object" ? state : null; if (!source || !source.buffers) { return normalizeScreenBuffer(null, { isAlt: false, bufferRows: 24 }); } return source.activeBuffer === "alt" ? source.buffers.alt : source.buffers.normal; } function getTerminalModeState(state) { return normalizeTerminalModes(state && state.modes ? state.modes : state); } function syncActiveBufferSnapshot(state, options, runtimeOptions) { const source = state && typeof state === "object" ? state : normalizeTerminalBufferState(null, options); const active = source.activeBuffer === "alt" ? source.buffers.alt : source.buffers.normal; const cloneRows = !(runtimeOptions && runtimeOptions.cloneRows === false); const activeCells = Array.isArray(active && active.cells) && active.cells.length > 0 ? active.cells : active && active.isAlt ? createEmptyRows(clampBufferRows(options && options.bufferRows)) : [[]]; source.cells = cloneRows ? cloneTerminalBufferRows(activeCells) : activeCells; source.ansiState = cloneAnsiState(active && active.ansiState); source.cursorRow = clampNonNegativeInteger(active && active.cursorRow, 0); source.cursorCol = clampNonNegativeInteger(active && active.cursorCol, 0); source.cursorHidden = !!(source.modes && source.modes.cursorHidden); source.applicationCursorKeys = !!(source.modes && source.modes.applicationCursorKeys); source.applicationKeypad = !!(source.modes && source.modes.applicationKeypad); source.bracketedPasteMode = !!(source.modes && source.modes.bracketedPasteMode); source.reverseWraparound = !!(source.modes && source.modes.reverseWraparound); source.sendFocus = !!(source.modes && source.modes.sendFocus); source.insertMode = !!(source.modes && source.modes.insertMode); source.activeBufferName = source.activeBuffer === "alt" ? "alt" : "normal"; return source; } function createEmptyTerminalBufferState(options) { return normalizeTerminalBufferState(null, options); } module.exports = { DEFAULT_TERMINAL_MODES, TERMINAL_BUFFER_STATE_VERSION, cloneAnsiState, cloneTerminalBufferRows, cloneTerminalBufferState, createEmptyRows, createEmptyTerminalBufferState, getActiveTerminalBuffer, getTerminalModeState, normalizeTerminalBufferState, syncActiveBufferSnapshot };