diff --git a/src/EscapeSequenceParser.test.ts b/src/EscapeSequenceParser.test.ts index ff32c5c3..e33e4b7e 100644 --- a/src/EscapeSequenceParser.test.ts +++ b/src/EscapeSequenceParser.test.ts @@ -1,4 +1,5 @@ -import { EscapeSequenceParser, ParserState, IDcsHandler } from './EscapeSequenceParser'; +import { ParserState, IDcsHandler } from './Types'; +import { EscapeSequenceParser } from './EscapeSequenceParser'; import * as chai from 'chai'; function r(a: number, b: number): string[] { diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts index 7de3857b..ea8e09ee 100644 --- a/src/EscapeSequenceParser.ts +++ b/src/EscapeSequenceParser.ts @@ -1,631 +1,544 @@ /** * TODO: - * - move interfaces to Types * - docs - * - test cases + * - extend test cases */ - -export interface IParsingState { - position: number; // position in string - code: number; // actual character code - currentState: ParserState; // current state - print: number; // print buffer start index - dcs: number; // dcs buffer start index - osc: string; // osc string buffer - collect: string; // collect buffer - params: number[]; // params buffer - abort: boolean; // should abort (default: false) -} - -export interface IPrintHandler { - (data: string, start: number, end: number): void; -} - -export interface IExecuteHandler { - (): void; -} - -export interface ICsiHandler { - (params: number[], collect: string): void; -} - -export interface IEscHandler { - (collect: string, flag: number): void; -} - -export interface IOscHandler { - (data: string): void; -} - -export interface IDcsHandler { - hook(collect: string, params: number[], flag: number): void; - put(data: string, start: number, end: number): void; - unhook(): void; -} - -export interface IErrorHandler { - (state: IParsingState): IParsingState; -} - -export interface IEscapeSequenceParser { - reset(): void; - parse(data: string): void; - - setPrintHandler(callback: IPrintHandler): void; - clearPrintHandler(): void; - - setExecuteHandler(flag: string, callback: IExecuteHandler): void; - clearExecuteHandler(flag: string): void; - setExecuteHandlerFallback(callback: (...params: any[]) => void): void; - - setCsiHandler(flag: string, callback: ICsiHandler): void; - clearCsiHandler(flag: string): void; - setCsiHandlerFallback(callback: (...params: any[]) => void): void; - - setEscHandler(collect: string, flag: string, callback: IEscHandler): void; - clearEscHandler(collect: string, flag: string): void; - setEscHandlerFallback(callback: (...params: any[]) => void): void; - - setOscHandler(ident: number, callback: IOscHandler): void; - clearOscHandler(ident: number): void; - setOscHandlerFallback(callback: (...params: any[]) => void): void; - - setDcsHandler(collect: string, flag: string, handler: IDcsHandler): void; - clearDcsHandler(collect: string, flag: string): void; - setDcsHandlerFallback(handler: IDcsHandler): void; - - setErrorHandler(callback: IErrorHandler): void; - clearErrorHandler(): void; - - // remove after revamp of InputHandler methods - setPrefixHandler(callback: (collect: string) => void): void; -} - - -// FSM states -export const enum ParserState { - GROUND = 0, - ESCAPE = 1, - ESCAPE_INTERMEDIATE = 2, - CSI_ENTRY = 3, - CSI_PARAM = 4, - CSI_INTERMEDIATE = 5, - CSI_IGNORE = 6, - SOS_PM_APC_STRING = 7, - OSC_STRING = 8, - DCS_ENTRY = 9, - DCS_PARAM = 10, - DCS_IGNORE = 11, - DCS_INTERMEDIATE = 12, - DCS_PASSTHROUGH = 13 -} - -// FSM actions -export const enum ParserAction { - IGNORE = 0, - ERROR = 1, - PRINT = 2, - EXECUTE = 3, - OSC_START = 4, - OSC_PUT = 5, - OSC_END = 6, - CSI_DISPATCH = 7, - PARAM = 8, - COLLECT = 9, - ESC_DISPATCH = 10, - CLEAR = 11, - DCS_HOOK = 12, - DCS_PUT = 13, - DCS_UNHOOK = 14 -} - +import { + ParserState, ParserAction, IParsingState, IPrintHandler, + IExecuteHandler, ICsiHandler, IEscHandler, IOscHandler, + IDcsHandler, IErrorHandler, IEscapeSequenceParser +} from './Types'; // number range macro function r(a: number, b: number): number[] { - let c = b - a; - let arr = new Array(c); - while (c--) { - arr[c] = --b; - } - return arr; + let c = b - a; + let arr = new Array(c); + while (c--) { + arr[c] = --b; + } + return arr; } - +/** + * Transition table for EscapeSequenceParser. + * NOTE: data in the underlying table is packed like this: + * currentState << 8 | characterCode --> action << 4 | nextState + */ export class TransitionTable { - public table: Uint8Array | number[]; + public table: Uint8Array | number[]; - constructor(length: number) { - this.table = (typeof Uint32Array === 'undefined') - ? new Array(length) - : new Uint32Array(length); - } + constructor(length: number) { + this.table = (typeof Uint32Array === 'undefined') + ? new Array(length) + : new Uint32Array(length); + } - add(inp: number, state: number, action: number | null, next: number | null): void { - this.table[state << 8 | inp] = ((action | 0) << 4) | ((next === undefined) ? state : next); - } + /** + * Add a new transition to the transition table. + * @param code input character code + * @param state current parser state + * @param action parser action to be done + * @param next next parser state + */ + add(code: number, state: number, action: number | null, next: number | null): void { + this.table[state << 8 | code] = ((action | 0) << 4) | ((next === undefined) ? state : next); + } - addMany(inps: number[], state: number, action: number | null, next: number | null): void { - for (let i = 0; i < inps.length; i++) { - this.add(inps[i], state, action, next); - } + /** + * Add transitions for multiple input characters codes. + * @param codes input character code array + * @param state current parser state + * @param action parser action to be done + * @param next next parser state + */ + addMany(codes: number[], state: number, action: number | null, next: number | null): void { + for (let i = 0; i < codes.length; i++) { + this.add(codes[i], state, action, next); } + } } -// default definitions of printable and executable characters +/** + * Default definitions for the VT500_TRANSITION_TABLE. + */ let PRINTABLES = r(0x20, 0x7f); let EXECUTABLES = r(0x00, 0x18); EXECUTABLES.push(0x19); EXECUTABLES.concat(r(0x1c, 0x20)); - -// default transition is [ParserAction.ERROR, ParserState.GROUND] const DEFAULT_TRANSITION = ParserAction.ERROR << 4 | ParserState.GROUND; -// default DEC/ANSI compatible state transition table -// as defined by https://vt100.net/emu/dec_ansi_parser +/** + * VT500 compatible transition table. + * Taken from https://vt100.net/emu/dec_ansi_parser. + */ export const VT500_TRANSITION_TABLE = (function (): TransitionTable { - let table: TransitionTable = new TransitionTable(4095); + let table: TransitionTable = new TransitionTable(4095); - let states: number[] = r(ParserState.GROUND, ParserState.DCS_PASSTHROUGH + 1); - let state: any; + let states: number[] = r(ParserState.GROUND, ParserState.DCS_PASSTHROUGH + 1); + let state: any; - // table with default transition [any] --> DEFAULT_TRANSITION - for (state in states) { - // table lookup is capped at 0xa0 in parse - // any higher will be treated by the error action - for (let code = 0; code < 160; ++code) { - table.add(code, state, ParserAction.ERROR, ParserState.GROUND); - } + // table with default transition [any] --> DEFAULT_TRANSITION + for (state in states) { + // NOTE: table lookup is capped at 0xa0 in parse to keep the table small + for (let code = 0; code < 160; ++code) { + table.add(code, state, ParserAction.ERROR, ParserState.GROUND); } - - // apply transitions - // printables - table.addMany(PRINTABLES, ParserState.GROUND, ParserAction.PRINT, ParserState.GROUND); - // global anywhere rules - for (state in states) { - table.addMany([0x18, 0x1a, 0x99, 0x9a], state, ParserAction.EXECUTE, ParserState.GROUND); - table.addMany(r(0x80, 0x90), state, ParserAction.EXECUTE, ParserState.GROUND); - table.addMany(r(0x90, 0x98), state, ParserAction.EXECUTE, ParserState.GROUND); - table.add(0x9c, state, ParserAction.IGNORE, ParserState.GROUND); // ST as terminator - table.add(0x1b, state, ParserAction.CLEAR, ParserState.ESCAPE); // ESC - table.add(0x9d, state, ParserAction.OSC_START, ParserState.OSC_STRING); // OSC - table.addMany([0x98, 0x9e, 0x9f], state, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING); - table.add(0x9b, state, ParserAction.CLEAR, ParserState.CSI_ENTRY); // CSI - table.add(0x90, state, ParserAction.CLEAR, ParserState.DCS_ENTRY); // DCS - } - // rules for executables and 7f - table.addMany(EXECUTABLES, ParserState.GROUND, ParserAction.EXECUTE, ParserState.GROUND); - table.addMany(EXECUTABLES, ParserState.ESCAPE, ParserAction.EXECUTE, ParserState.ESCAPE); - table.add(0x7f, ParserState.ESCAPE, ParserAction.IGNORE, ParserState.ESCAPE); - table.addMany(EXECUTABLES, ParserState.OSC_STRING, ParserAction.IGNORE, ParserState.OSC_STRING); - table.addMany(EXECUTABLES, ParserState.CSI_ENTRY, ParserAction.EXECUTE, ParserState.CSI_ENTRY); - table.add(0x7f, ParserState.CSI_ENTRY, ParserAction.IGNORE, ParserState.CSI_ENTRY); - table.addMany(EXECUTABLES, ParserState.CSI_PARAM, ParserAction.EXECUTE, ParserState.CSI_PARAM); - table.add(0x7f, ParserState.CSI_PARAM, ParserAction.IGNORE, ParserState.CSI_PARAM); - table.addMany(EXECUTABLES, ParserState.CSI_IGNORE, ParserAction.EXECUTE, ParserState.CSI_IGNORE); - table.addMany(EXECUTABLES, ParserState.CSI_INTERMEDIATE, ParserAction.EXECUTE, ParserState.CSI_INTERMEDIATE); - table.add(0x7f, ParserState.CSI_INTERMEDIATE, ParserAction.IGNORE, ParserState.CSI_INTERMEDIATE); - table.addMany(EXECUTABLES, ParserState.ESCAPE_INTERMEDIATE, ParserAction.EXECUTE, ParserState.ESCAPE_INTERMEDIATE); - table.add(0x7f, ParserState.ESCAPE_INTERMEDIATE, ParserAction.IGNORE, ParserState.ESCAPE_INTERMEDIATE); - // osc - table.add(0x5d, ParserState.ESCAPE, ParserAction.OSC_START, ParserState.OSC_STRING); - table.addMany(PRINTABLES, ParserState.OSC_STRING, ParserAction.OSC_PUT, ParserState.OSC_STRING); - table.add(0x7f, ParserState.OSC_STRING, ParserAction.OSC_PUT, ParserState.OSC_STRING); - table.addMany([0x9c, 0x1b, 0x18, 0x1a, 0x07], ParserState.OSC_STRING, ParserAction.OSC_END, ParserState.GROUND); - table.addMany(r(0x1c, 0x20), ParserState.OSC_STRING, ParserAction.IGNORE, ParserState.OSC_STRING); - // sos/pm/apc does nothing - table.addMany([0x58, 0x5e, 0x5f], ParserState.ESCAPE, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING); - table.addMany(PRINTABLES, ParserState.SOS_PM_APC_STRING, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING); - table.addMany(EXECUTABLES, ParserState.SOS_PM_APC_STRING, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING); - table.add(0x9c, ParserState.SOS_PM_APC_STRING, ParserAction.IGNORE, ParserState.GROUND); - // csi entries - table.add(0x5b, ParserState.ESCAPE, ParserAction.CLEAR, ParserState.CSI_ENTRY); - table.addMany(r(0x40, 0x7f), ParserState.CSI_ENTRY, ParserAction.CSI_DISPATCH, ParserState.GROUND); - table.addMany(r(0x30, 0x3a), ParserState.CSI_ENTRY, ParserAction.PARAM, ParserState.CSI_PARAM); - table.add(0x3b, ParserState.CSI_ENTRY, ParserAction.PARAM, ParserState.CSI_PARAM); - table.addMany([0x3c, 0x3d, 0x3e, 0x3f], ParserState.CSI_ENTRY, ParserAction.COLLECT, ParserState.CSI_PARAM); - table.addMany(r(0x30, 0x3a), ParserState.CSI_PARAM, ParserAction.PARAM, ParserState.CSI_PARAM); - table.add(0x3b, ParserState.CSI_PARAM, ParserAction.PARAM, ParserState.CSI_PARAM); - table.addMany(r(0x40, 0x7f), ParserState.CSI_PARAM, ParserAction.CSI_DISPATCH, ParserState.GROUND); - table.addMany([0x3a, 0x3c, 0x3d, 0x3e, 0x3f], ParserState.CSI_PARAM, ParserAction.IGNORE, ParserState.CSI_IGNORE); - table.addMany(r(0x20, 0x40), ParserState.CSI_IGNORE, null, ParserState.CSI_IGNORE); - table.add(0x7f, ParserState.CSI_IGNORE, null, ParserState.CSI_IGNORE); - table.addMany(r(0x40, 0x7f), ParserState.CSI_IGNORE, ParserAction.IGNORE, ParserState.GROUND); - table.add(0x3a, ParserState.CSI_ENTRY, ParserAction.IGNORE, ParserState.CSI_IGNORE); - table.addMany(r(0x20, 0x30), ParserState.CSI_ENTRY, ParserAction.COLLECT, ParserState.CSI_INTERMEDIATE); - table.addMany(r(0x20, 0x30), ParserState.CSI_INTERMEDIATE, ParserAction.COLLECT, ParserState.CSI_INTERMEDIATE); - table.addMany(r(0x30, 0x40), ParserState.CSI_INTERMEDIATE, ParserAction.IGNORE, ParserState.CSI_IGNORE); - table.addMany(r(0x40, 0x7f), ParserState.CSI_INTERMEDIATE, ParserAction.CSI_DISPATCH, ParserState.GROUND); - table.addMany(r(0x20, 0x30), ParserState.CSI_PARAM, ParserAction.COLLECT, ParserState.CSI_INTERMEDIATE); - // esc_intermediate - table.addMany(r(0x20, 0x30), ParserState.ESCAPE, ParserAction.COLLECT, ParserState.ESCAPE_INTERMEDIATE); - table.addMany(r(0x20, 0x30), ParserState.ESCAPE_INTERMEDIATE, ParserAction.COLLECT, ParserState.ESCAPE_INTERMEDIATE); - table.addMany(r(0x30, 0x7f), ParserState.ESCAPE_INTERMEDIATE, ParserAction.ESC_DISPATCH, ParserState.GROUND); - table.addMany(r(0x30, 0x50), ParserState.ESCAPE, ParserAction.ESC_DISPATCH, ParserState.GROUND); - table.addMany(r(0x51, 0x58), ParserState.ESCAPE, ParserAction.ESC_DISPATCH, ParserState.GROUND); - table.addMany([0x59, 0x5a, 0x5c], ParserState.ESCAPE, ParserAction.ESC_DISPATCH, ParserState.GROUND); - table.addMany(r(0x60, 0x7f), ParserState.ESCAPE, ParserAction.ESC_DISPATCH, ParserState.GROUND); - // dcs entry - table.add(0x50, ParserState.ESCAPE, ParserAction.CLEAR, ParserState.DCS_ENTRY); - table.addMany(EXECUTABLES, ParserState.DCS_ENTRY, ParserAction.IGNORE, ParserState.DCS_ENTRY); - table.add(0x7f, ParserState.DCS_ENTRY, ParserAction.IGNORE, ParserState.DCS_ENTRY); - table.addMany(r(0x1c, 0x20), ParserState.DCS_ENTRY, ParserAction.IGNORE, ParserState.DCS_ENTRY); - table.addMany(r(0x20, 0x30), ParserState.DCS_ENTRY, ParserAction.COLLECT, ParserState.DCS_INTERMEDIATE); - table.add(0x3a, ParserState.DCS_ENTRY, ParserAction.IGNORE, ParserState.DCS_IGNORE); - table.addMany(r(0x30, 0x3a), ParserState.DCS_ENTRY, ParserAction.PARAM, ParserState.DCS_PARAM); - table.add(0x3b, ParserState.DCS_ENTRY, ParserAction.PARAM, ParserState.DCS_PARAM); - table.addMany([0x3c, 0x3d, 0x3e, 0x3f], ParserState.DCS_ENTRY, ParserAction.COLLECT, ParserState.DCS_PARAM); - table.addMany(EXECUTABLES, ParserState.DCS_IGNORE, ParserAction.IGNORE, ParserState.DCS_IGNORE); - table.addMany(r(0x20, 0x80), ParserState.DCS_IGNORE, ParserAction.IGNORE, ParserState.DCS_IGNORE); - table.addMany(r(0x1c, 0x20), ParserState.DCS_IGNORE, ParserAction.IGNORE, ParserState.DCS_IGNORE); - table.addMany(EXECUTABLES, ParserState.DCS_PARAM, ParserAction.IGNORE, ParserState.DCS_PARAM); - table.add(0x7f, ParserState.DCS_PARAM, ParserAction.IGNORE, ParserState.DCS_PARAM); - table.addMany(r(0x1c, 0x20), ParserState.DCS_PARAM, ParserAction.IGNORE, ParserState.DCS_PARAM); - table.addMany(r(0x30, 0x3a), ParserState.DCS_PARAM, ParserAction.PARAM, ParserState.DCS_PARAM); - table.add(0x3b, ParserState.DCS_PARAM, ParserAction.PARAM, ParserState.DCS_PARAM); - table.addMany([0x3a, 0x3c, 0x3d, 0x3e, 0x3f], ParserState.DCS_PARAM, ParserAction.IGNORE, ParserState.DCS_IGNORE); - table.addMany(r(0x20, 0x30), ParserState.DCS_PARAM, ParserAction.COLLECT, ParserState.DCS_INTERMEDIATE); - table.addMany(EXECUTABLES, ParserState.DCS_INTERMEDIATE, ParserAction.IGNORE, ParserState.DCS_INTERMEDIATE); - table.add(0x7f, ParserState.DCS_INTERMEDIATE, ParserAction.IGNORE, ParserState.DCS_INTERMEDIATE); - table.addMany(r(0x1c, 0x20), ParserState.DCS_INTERMEDIATE, ParserAction.IGNORE, ParserState.DCS_INTERMEDIATE); - table.addMany(r(0x20, 0x30), ParserState.DCS_INTERMEDIATE, ParserAction.COLLECT, ParserState.DCS_INTERMEDIATE); - table.addMany(r(0x30, 0x40), ParserState.DCS_INTERMEDIATE, ParserAction.IGNORE, ParserState.DCS_IGNORE); - table.addMany(r(0x40, 0x7f), ParserState.DCS_INTERMEDIATE, ParserAction.DCS_HOOK, ParserState.DCS_PASSTHROUGH); - table.addMany(r(0x40, 0x7f), ParserState.DCS_PARAM, ParserAction.DCS_HOOK, ParserState.DCS_PASSTHROUGH); - table.addMany(r(0x40, 0x7f), ParserState.DCS_ENTRY, ParserAction.DCS_HOOK, ParserState.DCS_PASSTHROUGH); - table.addMany(EXECUTABLES, ParserState.DCS_PASSTHROUGH, ParserAction.DCS_PUT, ParserState.DCS_PASSTHROUGH); - table.addMany(PRINTABLES, ParserState.DCS_PASSTHROUGH, ParserAction.DCS_PUT, ParserState.DCS_PASSTHROUGH); - table.add(0x7f, ParserState.DCS_PASSTHROUGH, ParserAction.IGNORE, ParserState.DCS_PASSTHROUGH); - table.addMany([0x1b, 0x9c], ParserState.DCS_PASSTHROUGH, ParserAction.DCS_UNHOOK, ParserState.GROUND); - - return table; + } + // printables + table.addMany(PRINTABLES, ParserState.GROUND, ParserAction.PRINT, ParserState.GROUND); + // global anywhere rules + for (state in states) { + table.addMany([0x18, 0x1a, 0x99, 0x9a], state, ParserAction.EXECUTE, ParserState.GROUND); + table.addMany(r(0x80, 0x90), state, ParserAction.EXECUTE, ParserState.GROUND); + table.addMany(r(0x90, 0x98), state, ParserAction.EXECUTE, ParserState.GROUND); + table.add(0x9c, state, ParserAction.IGNORE, ParserState.GROUND); // ST as terminator + table.add(0x1b, state, ParserAction.CLEAR, ParserState.ESCAPE); // ESC + table.add(0x9d, state, ParserAction.OSC_START, ParserState.OSC_STRING); // OSC + table.addMany([0x98, 0x9e, 0x9f], state, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING); + table.add(0x9b, state, ParserAction.CLEAR, ParserState.CSI_ENTRY); // CSI + table.add(0x90, state, ParserAction.CLEAR, ParserState.DCS_ENTRY); // DCS + } + // rules for executables and 7f + table.addMany(EXECUTABLES, ParserState.GROUND, ParserAction.EXECUTE, ParserState.GROUND); + table.addMany(EXECUTABLES, ParserState.ESCAPE, ParserAction.EXECUTE, ParserState.ESCAPE); + table.add(0x7f, ParserState.ESCAPE, ParserAction.IGNORE, ParserState.ESCAPE); + table.addMany(EXECUTABLES, ParserState.OSC_STRING, ParserAction.IGNORE, ParserState.OSC_STRING); + table.addMany(EXECUTABLES, ParserState.CSI_ENTRY, ParserAction.EXECUTE, ParserState.CSI_ENTRY); + table.add(0x7f, ParserState.CSI_ENTRY, ParserAction.IGNORE, ParserState.CSI_ENTRY); + table.addMany(EXECUTABLES, ParserState.CSI_PARAM, ParserAction.EXECUTE, ParserState.CSI_PARAM); + table.add(0x7f, ParserState.CSI_PARAM, ParserAction.IGNORE, ParserState.CSI_PARAM); + table.addMany(EXECUTABLES, ParserState.CSI_IGNORE, ParserAction.EXECUTE, ParserState.CSI_IGNORE); + table.addMany(EXECUTABLES, ParserState.CSI_INTERMEDIATE, ParserAction.EXECUTE, ParserState.CSI_INTERMEDIATE); + table.add(0x7f, ParserState.CSI_INTERMEDIATE, ParserAction.IGNORE, ParserState.CSI_INTERMEDIATE); + table.addMany(EXECUTABLES, ParserState.ESCAPE_INTERMEDIATE, ParserAction.EXECUTE, ParserState.ESCAPE_INTERMEDIATE); + table.add(0x7f, ParserState.ESCAPE_INTERMEDIATE, ParserAction.IGNORE, ParserState.ESCAPE_INTERMEDIATE); + // osc + table.add(0x5d, ParserState.ESCAPE, ParserAction.OSC_START, ParserState.OSC_STRING); + table.addMany(PRINTABLES, ParserState.OSC_STRING, ParserAction.OSC_PUT, ParserState.OSC_STRING); + table.add(0x7f, ParserState.OSC_STRING, ParserAction.OSC_PUT, ParserState.OSC_STRING); + table.addMany([0x9c, 0x1b, 0x18, 0x1a, 0x07], ParserState.OSC_STRING, ParserAction.OSC_END, ParserState.GROUND); + table.addMany(r(0x1c, 0x20), ParserState.OSC_STRING, ParserAction.IGNORE, ParserState.OSC_STRING); + // sos/pm/apc does nothing + table.addMany([0x58, 0x5e, 0x5f], ParserState.ESCAPE, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING); + table.addMany(PRINTABLES, ParserState.SOS_PM_APC_STRING, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING); + table.addMany(EXECUTABLES, ParserState.SOS_PM_APC_STRING, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING); + table.add(0x9c, ParserState.SOS_PM_APC_STRING, ParserAction.IGNORE, ParserState.GROUND); + // csi entries + table.add(0x5b, ParserState.ESCAPE, ParserAction.CLEAR, ParserState.CSI_ENTRY); + table.addMany(r(0x40, 0x7f), ParserState.CSI_ENTRY, ParserAction.CSI_DISPATCH, ParserState.GROUND); + table.addMany(r(0x30, 0x3a), ParserState.CSI_ENTRY, ParserAction.PARAM, ParserState.CSI_PARAM); + table.add(0x3b, ParserState.CSI_ENTRY, ParserAction.PARAM, ParserState.CSI_PARAM); + table.addMany([0x3c, 0x3d, 0x3e, 0x3f], ParserState.CSI_ENTRY, ParserAction.COLLECT, ParserState.CSI_PARAM); + table.addMany(r(0x30, 0x3a), ParserState.CSI_PARAM, ParserAction.PARAM, ParserState.CSI_PARAM); + table.add(0x3b, ParserState.CSI_PARAM, ParserAction.PARAM, ParserState.CSI_PARAM); + table.addMany(r(0x40, 0x7f), ParserState.CSI_PARAM, ParserAction.CSI_DISPATCH, ParserState.GROUND); + table.addMany([0x3a, 0x3c, 0x3d, 0x3e, 0x3f], ParserState.CSI_PARAM, ParserAction.IGNORE, ParserState.CSI_IGNORE); + table.addMany(r(0x20, 0x40), ParserState.CSI_IGNORE, null, ParserState.CSI_IGNORE); + table.add(0x7f, ParserState.CSI_IGNORE, null, ParserState.CSI_IGNORE); + table.addMany(r(0x40, 0x7f), ParserState.CSI_IGNORE, ParserAction.IGNORE, ParserState.GROUND); + table.add(0x3a, ParserState.CSI_ENTRY, ParserAction.IGNORE, ParserState.CSI_IGNORE); + table.addMany(r(0x20, 0x30), ParserState.CSI_ENTRY, ParserAction.COLLECT, ParserState.CSI_INTERMEDIATE); + table.addMany(r(0x20, 0x30), ParserState.CSI_INTERMEDIATE, ParserAction.COLLECT, ParserState.CSI_INTERMEDIATE); + table.addMany(r(0x30, 0x40), ParserState.CSI_INTERMEDIATE, ParserAction.IGNORE, ParserState.CSI_IGNORE); + table.addMany(r(0x40, 0x7f), ParserState.CSI_INTERMEDIATE, ParserAction.CSI_DISPATCH, ParserState.GROUND); + table.addMany(r(0x20, 0x30), ParserState.CSI_PARAM, ParserAction.COLLECT, ParserState.CSI_INTERMEDIATE); + // esc_intermediate + table.addMany(r(0x20, 0x30), ParserState.ESCAPE, ParserAction.COLLECT, ParserState.ESCAPE_INTERMEDIATE); + table.addMany(r(0x20, 0x30), ParserState.ESCAPE_INTERMEDIATE, ParserAction.COLLECT, ParserState.ESCAPE_INTERMEDIATE); + table.addMany(r(0x30, 0x7f), ParserState.ESCAPE_INTERMEDIATE, ParserAction.ESC_DISPATCH, ParserState.GROUND); + table.addMany(r(0x30, 0x50), ParserState.ESCAPE, ParserAction.ESC_DISPATCH, ParserState.GROUND); + table.addMany(r(0x51, 0x58), ParserState.ESCAPE, ParserAction.ESC_DISPATCH, ParserState.GROUND); + table.addMany([0x59, 0x5a, 0x5c], ParserState.ESCAPE, ParserAction.ESC_DISPATCH, ParserState.GROUND); + table.addMany(r(0x60, 0x7f), ParserState.ESCAPE, ParserAction.ESC_DISPATCH, ParserState.GROUND); + // dcs entry + table.add(0x50, ParserState.ESCAPE, ParserAction.CLEAR, ParserState.DCS_ENTRY); + table.addMany(EXECUTABLES, ParserState.DCS_ENTRY, ParserAction.IGNORE, ParserState.DCS_ENTRY); + table.add(0x7f, ParserState.DCS_ENTRY, ParserAction.IGNORE, ParserState.DCS_ENTRY); + table.addMany(r(0x1c, 0x20), ParserState.DCS_ENTRY, ParserAction.IGNORE, ParserState.DCS_ENTRY); + table.addMany(r(0x20, 0x30), ParserState.DCS_ENTRY, ParserAction.COLLECT, ParserState.DCS_INTERMEDIATE); + table.add(0x3a, ParserState.DCS_ENTRY, ParserAction.IGNORE, ParserState.DCS_IGNORE); + table.addMany(r(0x30, 0x3a), ParserState.DCS_ENTRY, ParserAction.PARAM, ParserState.DCS_PARAM); + table.add(0x3b, ParserState.DCS_ENTRY, ParserAction.PARAM, ParserState.DCS_PARAM); + table.addMany([0x3c, 0x3d, 0x3e, 0x3f], ParserState.DCS_ENTRY, ParserAction.COLLECT, ParserState.DCS_PARAM); + table.addMany(EXECUTABLES, ParserState.DCS_IGNORE, ParserAction.IGNORE, ParserState.DCS_IGNORE); + table.addMany(r(0x20, 0x80), ParserState.DCS_IGNORE, ParserAction.IGNORE, ParserState.DCS_IGNORE); + table.addMany(r(0x1c, 0x20), ParserState.DCS_IGNORE, ParserAction.IGNORE, ParserState.DCS_IGNORE); + table.addMany(EXECUTABLES, ParserState.DCS_PARAM, ParserAction.IGNORE, ParserState.DCS_PARAM); + table.add(0x7f, ParserState.DCS_PARAM, ParserAction.IGNORE, ParserState.DCS_PARAM); + table.addMany(r(0x1c, 0x20), ParserState.DCS_PARAM, ParserAction.IGNORE, ParserState.DCS_PARAM); + table.addMany(r(0x30, 0x3a), ParserState.DCS_PARAM, ParserAction.PARAM, ParserState.DCS_PARAM); + table.add(0x3b, ParserState.DCS_PARAM, ParserAction.PARAM, ParserState.DCS_PARAM); + table.addMany([0x3a, 0x3c, 0x3d, 0x3e, 0x3f], ParserState.DCS_PARAM, ParserAction.IGNORE, ParserState.DCS_IGNORE); + table.addMany(r(0x20, 0x30), ParserState.DCS_PARAM, ParserAction.COLLECT, ParserState.DCS_INTERMEDIATE); + table.addMany(EXECUTABLES, ParserState.DCS_INTERMEDIATE, ParserAction.IGNORE, ParserState.DCS_INTERMEDIATE); + table.add(0x7f, ParserState.DCS_INTERMEDIATE, ParserAction.IGNORE, ParserState.DCS_INTERMEDIATE); + table.addMany(r(0x1c, 0x20), ParserState.DCS_INTERMEDIATE, ParserAction.IGNORE, ParserState.DCS_INTERMEDIATE); + table.addMany(r(0x20, 0x30), ParserState.DCS_INTERMEDIATE, ParserAction.COLLECT, ParserState.DCS_INTERMEDIATE); + table.addMany(r(0x30, 0x40), ParserState.DCS_INTERMEDIATE, ParserAction.IGNORE, ParserState.DCS_IGNORE); + table.addMany(r(0x40, 0x7f), ParserState.DCS_INTERMEDIATE, ParserAction.DCS_HOOK, ParserState.DCS_PASSTHROUGH); + table.addMany(r(0x40, 0x7f), ParserState.DCS_PARAM, ParserAction.DCS_HOOK, ParserState.DCS_PASSTHROUGH); + table.addMany(r(0x40, 0x7f), ParserState.DCS_ENTRY, ParserAction.DCS_HOOK, ParserState.DCS_PASSTHROUGH); + table.addMany(EXECUTABLES, ParserState.DCS_PASSTHROUGH, ParserAction.DCS_PUT, ParserState.DCS_PASSTHROUGH); + table.addMany(PRINTABLES, ParserState.DCS_PASSTHROUGH, ParserAction.DCS_PUT, ParserState.DCS_PASSTHROUGH); + table.add(0x7f, ParserState.DCS_PASSTHROUGH, ParserAction.IGNORE, ParserState.DCS_PASSTHROUGH); + table.addMany([0x1b, 0x9c], ParserState.DCS_PASSTHROUGH, ParserAction.DCS_UNHOOK, ParserState.GROUND); + return table; })(); -// fallback dummy DCS handler, does really nothing +/** + * Dummy DCS handler as default fallback. + */ class DcsDummy implements IDcsHandler { - hook(collect: string, params: number[], flag: number): void {} - put(data: string, start: number, end: number): void {} - unhook(): void {} + hook(collect: string, params: number[], flag: number): void { } + put(data: string, start: number, end: number): void { } + unhook(): void { } } +/** + * EscapeSequenceParser. + * This class implements the ANSI/DEC compatible parser described by + * Paul Williams (https://vt100.net/emu/dec_ansi_parser). + * NOTE: The parameter element notation is currently not supported. + */ export class EscapeSequenceParser implements IEscapeSequenceParser { - public initialState: number; - public currentState: number; - readonly transitions: TransitionTable; + public initialState: number; + public currentState: number; + readonly transitions: TransitionTable; - // buffers over several parse calls - public osc: string; - public params: number[]; - public collect: string; + // buffers over several parse calls + // FIXME: make those protected (needs workaround in tests) + public osc: string; + public params: number[]; + public collect: string; - // callback slots - protected _printHandler: IPrintHandler; - protected _executeHandlers: any; - protected _csiHandlers: any; - protected _escHandlers: any; - protected _oscHandlers: any; - protected _dcsHandlers: any; - protected _activeDcsHandler: IDcsHandler | null; - protected _errorHandler: IErrorHandler; + // callback slots + protected _printHandler: IPrintHandler; + protected _executeHandlers: any; + protected _csiHandlers: any; + protected _escHandlers: any; + protected _oscHandlers: any; + protected _dcsHandlers: any; + protected _activeDcsHandler: IDcsHandler | null; + protected _errorHandler: IErrorHandler; - // fallback handlers - protected _printHandlerFb: IPrintHandler; - protected _executeHandlerFb: (...params: any[]) => void; - protected _csiHandlerFb: (...params: any[]) => void; - protected _escHandlerFb: (...params: any[]) => void; - protected _oscHandlerFb: (...params: any[]) => void; - protected _dcsHandlerFb: IDcsHandler; - protected _errorHandlerFb: IErrorHandler; + // fallback handlers + protected _printHandlerFb: IPrintHandler; + protected _executeHandlerFb: (...params: any[]) => void; + protected _csiHandlerFb: (...params: any[]) => void; + protected _escHandlerFb: (...params: any[]) => void; + protected _oscHandlerFb: (...params: any[]) => void; + protected _dcsHandlerFb: IDcsHandler; + protected _errorHandlerFb: IErrorHandler; - // FIXME: to be removed - protected _tempPrefixHandler: any; + // FIXME: to be removed + protected _tempPrefixHandler: any; - constructor(transitions: TransitionTable = VT500_TRANSITION_TABLE) { - this.initialState = ParserState.GROUND; - this.currentState = this.initialState; - this.transitions = transitions; - this.osc = ''; - this.params = [0]; - this.collect = ''; + constructor(transitions: TransitionTable = VT500_TRANSITION_TABLE) { + this.initialState = ParserState.GROUND; + this.currentState = this.initialState; + this.transitions = transitions; + this.osc = ''; + this.params = [0]; + this.collect = ''; - // set default fallback handlers - this._printHandlerFb = (data, start, end): void => {}; - this._executeHandlerFb = (...params: any[]): void => {}; - this._csiHandlerFb = (...params: any[]): void => {}; - this._escHandlerFb = (...params: any[]): void => {}; - this._oscHandlerFb = (...params: any[]): void => {}; - this._dcsHandlerFb = new DcsDummy; - this._errorHandlerFb = (state: IParsingState): IParsingState => state; + // set default fallback handlers + this._printHandlerFb = (data, start, end): void => { }; + this._executeHandlerFb = (...params: any[]): void => { }; + this._csiHandlerFb = (...params: any[]): void => { }; + this._escHandlerFb = (...params: any[]): void => { }; + this._oscHandlerFb = (...params: any[]): void => { }; + this._dcsHandlerFb = new DcsDummy; + this._errorHandlerFb = (state: IParsingState): IParsingState => state; + this._printHandler = this._printHandlerFb; + this._executeHandlers = Object.create(null); + this._csiHandlers = Object.create(null); + this._escHandlers = Object.create(null); + this._oscHandlers = Object.create(null); + this._dcsHandlers = Object.create(null); + this._activeDcsHandler = null; + this._errorHandler = this._errorHandlerFb; + } - this._printHandler = this._printHandlerFb; - this._executeHandlers = Object.create(null); - this._csiHandlers = Object.create(null); - this._escHandlers = Object.create(null); - this._oscHandlers = Object.create(null); - this._dcsHandlers = Object.create(null); - this._activeDcsHandler = null; - this._errorHandler = this._errorHandlerFb; - } + setPrintHandler(callback: IPrintHandler): void { + this._printHandler = callback; + } + clearPrintHandler(): void { + this._printHandler = this._printHandlerFb; + } - setPrintHandler(callback: IPrintHandler): void { - this._printHandler = callback; - } - clearPrintHandler(): void { - this._printHandler = this._printHandlerFb; - } + setExecuteHandler(flag: string, callback: IExecuteHandler): void { + this._executeHandlers[flag.charCodeAt(0)] = callback; + } + clearExecuteHandler(flag: string): void { + if (this._executeHandlers[flag.charCodeAt(0)]) delete this._executeHandlers[flag.charCodeAt(0)]; + } + setExecuteHandlerFallback(callback: (...params: any[]) => void): void { + this._executeHandlerFb = callback; + } - setExecuteHandler(flag: string, callback: IExecuteHandler): void { - this._executeHandlers[flag.charCodeAt(0)] = callback; - } - clearExecuteHandler(flag: string): void { - if (this._executeHandlers[flag.charCodeAt(0)]) delete this._executeHandlers[flag.charCodeAt(0)]; - } - setExecuteHandlerFallback(callback: (...params: any[]) => void): void { - this._executeHandlerFb = callback; - } + setCsiHandler(flag: string, callback: ICsiHandler): void { + this._csiHandlers[flag.charCodeAt(0)] = callback; + } + clearCsiHandler(flag: string): void { + if (this._csiHandlers[flag.charCodeAt(0)]) delete this._csiHandlers[flag.charCodeAt(0)]; + } + setCsiHandlerFallback(callback: (...params: any[]) => void): void { + this._csiHandlerFb = callback; + } - setCsiHandler(flag: string, callback: ICsiHandler): void { - this._csiHandlers[flag.charCodeAt(0)] = callback; - } - clearCsiHandler(flag: string): void { - if (this._csiHandlers[flag.charCodeAt(0)]) delete this._csiHandlers[flag.charCodeAt(0)]; - } - setCsiHandlerFallback(callback: (...params: any[]) => void): void { - this._csiHandlerFb = callback; - } + setEscHandler(collect: string, flag: string, callback: IEscHandler): void { + this._escHandlers[collect + flag] = callback; + } + clearEscHandler(collect: string, flag: string): void { + if (this._escHandlers[collect + flag]) delete this._escHandlers[collect + flag]; + } + setEscHandlerFallback(callback: (...params: any[]) => void): void { + this._escHandlerFb = callback; + } - setEscHandler(collect: string, flag: string, callback: IEscHandler): void { - this._escHandlers[collect + flag] = callback; - } - clearEscHandler(collect: string, flag: string): void { - if (this._escHandlers[collect + flag]) delete this._escHandlers[collect + flag]; - } - setEscHandlerFallback(callback: (...params: any[]) => void): void { - this._escHandlerFb = callback; - } + setOscHandler(ident: number, callback: IOscHandler): void { + this._oscHandlers[ident] = callback; + } + clearOscHandler(ident: number): void { + if (this._oscHandlers[ident]) delete this._oscHandlers[ident]; + } + setOscHandlerFallback(callback: (...params: any[]) => void): void { + this._oscHandlerFb = callback; + } - setOscHandler(ident: number, callback: IOscHandler): void { - this._oscHandlers[ident] = callback; - } - clearOscHandler(ident: number): void { - if (this._oscHandlers[ident]) delete this._oscHandlers[ident]; - } - setOscHandlerFallback(callback: (...params: any[]) => void): void { - this._oscHandlerFb = callback; - } + setDcsHandler(collect: string, flag: string, handler: IDcsHandler): void { + this._dcsHandlers[collect + flag] = handler; + } + clearDcsHandler(collect: string, flag: string): void { + if (this._dcsHandlers[collect + flag]) delete this._dcsHandlers[collect + flag]; + } + setDcsHandlerFallback(handler: IDcsHandler): void { + this._dcsHandlerFb = handler; + } - setDcsHandler(collect: string, flag: string, handler: IDcsHandler): void { - this._dcsHandlers[collect + flag] = handler; - } - clearDcsHandler(collect: string, flag: string): void { - if (this._dcsHandlers[collect + flag]) delete this._dcsHandlers[collect + flag]; - } - setDcsHandlerFallback(handler: IDcsHandler): void { - this._dcsHandlerFb = handler; - } + setErrorHandler(callback: IErrorHandler): void { + this._errorHandler = callback; + } + clearErrorHandler(): void { + this._errorHandler = this._errorHandlerFb; + } - setErrorHandler(callback: IErrorHandler): void { - this._errorHandler = callback; - } - clearErrorHandler(): void { - this._errorHandler = this._errorHandlerFb; - } + // FIXME: to be removed + setPrefixHandler(callback: (collect: string) => void): void { + this._tempPrefixHandler = callback; + } - // FIXME: to be removed - setPrefixHandler(callback: (collect: string) => void): void { - this._tempPrefixHandler = callback; - } + reset(): void { + this.currentState = this.initialState; + this.osc = ''; + this.params = [0]; + this.collect = ''; + } - reset(): void { - this.currentState = this.initialState; - this.osc = ''; - this.params = [0]; - this.collect = ''; - } + parse(data: string): void { + let code = 0; + let transition = 0; + let error = false; + let currentState = this.currentState; - parse(data: string): void { - let code = 0; - let transition = 0; - let error = false; - let currentState = this.currentState; + // local buffers + let print = -1; + let dcs = -1; + let osc = this.osc; + let collect = this.collect; + let params = this.params; + const table: Uint8Array | number[] = this.transitions.table; + let dcsHandler: IDcsHandler | null = this._activeDcsHandler; + let ident: string = ''; // ugly workaround for ESC and DCS lookup keys - // local buffers - let print = -1; - let dcs = -1; - let osc = this.osc; - let collect = this.collect; - let params = this.params; - const table: Uint8Array | number[] = this.transitions.table; - let dcsHandler: IDcsHandler | null = this._activeDcsHandler; - let ident: string = ''; // ugly workaround for ESC and DCS lookup keys + // process input string + const l = data.length; + for (let i = 0; i < l; ++i) { + code = data.charCodeAt(i); - // process input string - const l = data.length; - for (let i = 0; i < l; ++i) { - code = data.charCodeAt(i); + // shortcut for most chars (print action) + if (currentState === ParserState.GROUND && code > 0x1f && code < 0x80) { + print = (~print) ? print : i; + do code = data.charCodeAt(++i); + while (i < l && code > 0x1f && code < 0x80); + i--; + continue; + } - // shortcut for most chars (print action) - if (currentState === ParserState.GROUND && code > 0x1f && code < 0x80) { + // shortcut for CSI params + if (currentState === ParserState.CSI_PARAM && (code > 0x2f && code < 0x39)) { + params[params.length - 1] = params[params.length - 1] * 10 + code - 48; + continue; + } + + // normal transition & action lookup + transition = (code < 0xa0) ? (table[currentState << 8 | code]) : DEFAULT_TRANSITION; + switch (transition >> 4) { + case ParserAction.PRINT: + print = (~print) ? print : i; + break; + case ParserAction.EXECUTE: + if (~print) { + this._printHandler(data, print, i); + print = -1; + } + if (this._executeHandlers[code]) this._executeHandlers[code](); + else this._executeHandlerFb(code); + break; + case ParserAction.IGNORE: + // handle leftover print or dcs chars + if (~print) { + this._printHandler(data, print, i); + print = -1; + } else if (~dcs) { + dcsHandler.put(data, dcs, i); + dcs = -1; + } + break; + case ParserAction.ERROR: + // chars higher than 0x9f are handled by this action to + // keep the lookup table small + if (code > 0x9f) { + switch (currentState) { + case ParserState.GROUND: print = (~print) ? print : i; - do code = data.charCodeAt(++i); - while (i < l && code > 0x1f && code < 0x80); - i--; - continue; + break; + case ParserState.OSC_STRING: + osc += String.fromCharCode(code); + transition |= ParserState.OSC_STRING; + break; + case ParserState.CSI_IGNORE: + transition |= ParserState.CSI_IGNORE; + break; + case ParserState.DCS_IGNORE: + transition |= ParserState.DCS_IGNORE; + break; + case ParserState.DCS_PASSTHROUGH: + dcs = (~dcs) ? dcs : i; + transition |= ParserState.DCS_PASSTHROUGH; + break; + default: + error = true; } - - // shortcut for CSI params - if (currentState === ParserState.CSI_PARAM && (code > 0x2f && code < 0x39)) { - params[params.length - 1] = params[params.length - 1] * 10 + code - 48; - continue; + } else { + error = true; + } + // if we end up here a real error happened + if (error) { + let inject: IParsingState = this._errorHandler( + { + position: i, + code, + currentState, + print, + dcs, + osc, + collect, + params, + abort: false + }); + if (inject.abort) return; + // FIXME: inject return values + error = false; + } + break; + case ParserAction.CSI_DISPATCH: + this._tempPrefixHandler(collect); // FIXME: to be removed + if (this._csiHandlers[code]) this._csiHandlers[code](params, collect); + else this._csiHandlerFb(collect, params, code); + break; + case ParserAction.PARAM: + if (code === 0x3b) params.push(0); + else params[params.length - 1] = params[params.length - 1] * 10 + code - 48; + break; + case ParserAction.COLLECT: + collect += String.fromCharCode(code); + break; + case ParserAction.ESC_DISPATCH: + ident = collect + String.fromCharCode(code); + if (this._escHandlers[ident]) this._escHandlers[ident](collect, code); + else this._escHandlerFb(collect, code); + break; + case ParserAction.CLEAR: + if (~print) { + this._printHandler(data, print, i); + print = -1; + } + osc = ''; + params = [0]; + collect = ''; + dcs = -1; + break; + case ParserAction.DCS_HOOK: + ident = collect + String.fromCharCode(code); + if (this._dcsHandlers[ident]) dcsHandler = this._dcsHandlers[ident]; + else dcsHandler = this._dcsHandlerFb; + dcsHandler.hook(collect, params, code); + break; + case ParserAction.DCS_PUT: + dcs = (~dcs) ? dcs : i; + break; + case ParserAction.DCS_UNHOOK: + if (dcsHandler) { + if (~dcs) dcsHandler.put(data, dcs, i); + dcsHandler.unhook(); + dcsHandler = null; + } + if (code === 0x1b) transition |= ParserState.ESCAPE; + osc = ''; + params = [0]; + collect = ''; + dcs = -1; + break; + case ParserAction.OSC_START: + if (~print) { + this._printHandler(data, print, i); + print = -1; + } + osc = ''; + break; + case ParserAction.OSC_PUT: + osc += data.charAt(i); + break; + case ParserAction.OSC_END: + if (osc && code !== 0x18 && code !== 0x1a) { + let idx = osc.indexOf(';'); + if (idx === -1) { + this._oscHandlerFb(-1, osc); // this is an error (malformed OSC) + } else { + let identifier = parseInt(osc.substring(0, idx)); // NaN not handled here + let content = osc.substring(idx + 1); + if (this._oscHandlers[identifier]) this._oscHandlers[identifier](content); + else this._oscHandlerFb(identifier, content); } - - // normal transition & action lookup - transition = (code < 0xa0) ? (table[currentState << 8 | code]) : DEFAULT_TRANSITION; - switch (transition >> 4) { - case ParserAction.PRINT: - print = (~print) ? print : i; - break; - case ParserAction.EXECUTE: - if (~print) { - this._printHandler(data, print, i); - print = -1; - } - if (this._executeHandlers[code]) this._executeHandlers[code](); - else this._executeHandlerFb(code); - break; - case ParserAction.IGNORE: - // handle leftover print or dcs chars - if (~print) { - this._printHandler(data, print, i); - print = -1; - } else if (~dcs) { - dcsHandler.put(data, dcs, i); - dcs = -1; - } - break; - case ParserAction.ERROR: - // chars higher than 0x9f are handled by this action to - // keep the lookup table small - if (code > 0x9f) { - switch (currentState) { - case ParserState.GROUND: - print = (~print) ? print : i; - break; - case ParserState.OSC_STRING: - osc += String.fromCharCode(code); - transition |= ParserState.OSC_STRING; - break; - case ParserState.CSI_IGNORE: - transition |= ParserState.CSI_IGNORE; - break; - case ParserState.DCS_IGNORE: - transition |= ParserState.DCS_IGNORE; - break; - case ParserState.DCS_PASSTHROUGH: - dcs = (~dcs) ? dcs : i; - transition |= ParserState.DCS_PASSTHROUGH; - break; - default: - error = true; - } - } else { - error = true; - } - // if we end up here a real error happened - if (error) { - let inject: IParsingState = this._errorHandler( - { - position: i, - code, - currentState, - print, - dcs, - osc, - collect, - params, - abort: false - }); - if (inject.abort) return; - // FIXME: inject return values - error = false; - } - break; - case ParserAction.CSI_DISPATCH: - this._tempPrefixHandler(collect); // FIXME: to be removed - if (this._csiHandlers[code]) this._csiHandlers[code](params, collect); - else this._csiHandlerFb(collect, params, code); - break; - case ParserAction.PARAM: - if (code === 0x3b) params.push(0); - else params[params.length - 1] = params[params.length - 1] * 10 + code - 48; - break; - case ParserAction.COLLECT: - collect += String.fromCharCode(code); - break; - case ParserAction.ESC_DISPATCH: - ident = collect + String.fromCharCode(code); - if (this._escHandlers[ident]) this._escHandlers[ident](collect, code); - else this._escHandlerFb(collect, code); - break; - case ParserAction.CLEAR: - if (~print) { - this._printHandler(data, print, i); - print = -1; - } - osc = ''; - params = [0]; - collect = ''; - dcs = -1; - break; - case ParserAction.DCS_HOOK: - ident = collect + String.fromCharCode(code); - if (this._dcsHandlers[ident]) dcsHandler = this._dcsHandlers[ident]; - else dcsHandler = this._dcsHandlerFb; - dcsHandler.hook(collect, params, code); - break; - case ParserAction.DCS_PUT: - dcs = (~dcs) ? dcs : i; - break; - case ParserAction.DCS_UNHOOK: - if (dcsHandler) { - if (~dcs) dcsHandler.put(data, dcs, i); - dcsHandler.unhook(); - dcsHandler = null; - } - if (code === 0x1b) transition |= ParserState.ESCAPE; - osc = ''; - params = [0]; - collect = ''; - dcs = -1; - break; - case ParserAction.OSC_START: - if (~print) { - this._printHandler(data, print, i); - print = -1; - } - osc = ''; - break; - case ParserAction.OSC_PUT: - osc += data.charAt(i); - break; - case ParserAction.OSC_END: - if (osc && code !== 0x18 && code !== 0x1a) { - let idx = osc.indexOf(';'); - if (idx === -1) { - this._oscHandlerFb(-1, osc); // this is an error - } else { - let identifier = parseInt(osc.substring(0, idx)); - let content = osc.substring(idx + 1); - if (this._oscHandlers[identifier]) this._oscHandlers[identifier](content); - else this._oscHandlerFb(identifier, content); - } - } - if (code === 0x1b) transition |= ParserState.ESCAPE; - osc = ''; - params = [0]; - collect = ''; - dcs = -1; - break; - } - currentState = transition & 15; - } - - // push leftover pushable buffers to terminal - if (currentState === ParserState.GROUND && ~print) { - this._printHandler(data, print, data.length); - } else if (currentState === ParserState.DCS_PASSTHROUGH && ~dcs && dcsHandler) { - dcsHandler.put(data, dcs, data.length); - } - - // save non pushable buffers - this.osc = osc; - this.collect = collect; - this.params = params; - - // save active dcs handler reference - this._activeDcsHandler = dcsHandler; - - // save state - this.currentState = currentState; + } + if (code === 0x1b) transition |= ParserState.ESCAPE; + osc = ''; + params = [0]; + collect = ''; + dcs = -1; + break; + } + currentState = transition & 15; } + + // push leftover pushable buffers to terminal + if (currentState === ParserState.GROUND && ~print) { + this._printHandler(data, print, data.length); + } else if (currentState === ParserState.DCS_PASSTHROUGH && ~dcs && dcsHandler) { + dcsHandler.put(data, dcs, data.length); + } + + // save non pushable buffers + this.osc = osc; + this.collect = collect; + this.params = params; + + // save active dcs handler reference + this._activeDcsHandler = dcsHandler; + + // save state + this.currentState = currentState; + } } diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 2561260d..8d023dab 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -23,7 +23,10 @@ export class InputHandler implements IInputHandler { private _parser: EscapeSequenceParser; private _surrogateHigh: string; - constructor(protected _terminal: any) { + constructor(protected _terminal: any) { } + + // FIXME: temp workaround to get tests working again + init(): void { this._parser = new EscapeSequenceParser; // FIXME: maybe as ctor argument this._surrogateHigh = ''; diff --git a/src/Terminal.ts b/src/Terminal.ts index fa2ce01e..078266d1 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -330,6 +330,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this._userScrolling = false; this._inputHandler = new InputHandler(this); + this._inputHandler.init(); // Reuse renderer if the Terminal is being recreated via a reset call. this.renderer = this.renderer || null; this.selectionManager = this.selectionManager || null; diff --git a/src/Types.ts b/src/Types.ts index 5e2ee907..08e9f592 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -227,7 +227,7 @@ export interface ILinkifierAccessor { } export interface IMouseHelper { - getCoords(event: {pageX: number, pageY: number}, element: HTMLElement, charMeasure: ICharMeasure, lineHeight: number, colCount: number, rowCount: number, isSelection?: boolean): [number, number]; + getCoords(event: { pageX: number, pageY: number }, element: HTMLElement, charMeasure: ICharMeasure, lineHeight: number, colCount: number, rowCount: number, isSelection?: boolean): [number, number]; getRawByteCoords(event: MouseEvent, element: HTMLElement, charMeasure: ICharMeasure, lineHeight: number, colCount: number, rowCount: number): { x: number, y: number }; } @@ -356,3 +356,173 @@ export interface IBrowser { export interface ISoundManager { playBellSound(): void; } + +/** + * Internal states of EscapeSequenceParser. + */ +export const enum ParserState { + GROUND = 0, + ESCAPE = 1, + ESCAPE_INTERMEDIATE = 2, + CSI_ENTRY = 3, + CSI_PARAM = 4, + CSI_INTERMEDIATE = 5, + CSI_IGNORE = 6, + SOS_PM_APC_STRING = 7, + OSC_STRING = 8, + DCS_ENTRY = 9, + DCS_PARAM = 10, + DCS_IGNORE = 11, + DCS_INTERMEDIATE = 12, + DCS_PASSTHROUGH = 13 +} + +/** +* Internal actions of EscapeSequenceParser. +*/ +export const enum ParserAction { + IGNORE = 0, + ERROR = 1, + PRINT = 2, + EXECUTE = 3, + OSC_START = 4, + OSC_PUT = 5, + OSC_END = 6, + CSI_DISPATCH = 7, + PARAM = 8, + COLLECT = 9, + ESC_DISPATCH = 10, + CLEAR = 11, + DCS_HOOK = 12, + DCS_PUT = 13, + DCS_UNHOOK = 14 +} + +/** + * Internal state of EscapeSequenceParser. + * Used as argument to the error handler to allow + * introspection at runtime on parse errors. + * Return it with altered values to recover from + * faulty states (not yet supported). + * Set `abort` to `true` to abort the current parsing. + */ +export interface IParsingState { + // position in parse string + position: number; + // actual character code + code: number; + // current parser state + currentState: ParserState; + // print buffer start index (-1 for not set) + print: number; + // dcs buffer start index (-1 for not set) + dcs: number; + // osc string buffer + osc: string; + // collect buffer with intermediate characters + collect: string; + // params buffer + params: number[]; + // should abort (default: false) + abort: boolean; +} + +/** +* Print handler signature for EscapeSequenceParser. +* `start` and `end` are the start and end indices of +* printable characters in `data`. +*/ +export interface IPrintHandler { + (data: string, start: number, end: number): void; +} + +/** +* Execute handler signature for EscapeSequenceParser. +*/ +export interface IExecuteHandler { + (): void; +} + +/** +* CSI handler signature for EscapeSequenceParser. +* `collect` contains the intermediate characters +* of the escape sequence. +*/ +export interface ICsiHandler { + (params: number[], collect: string): void; +} + +/** +* ESC handler signature for EscapeSequenceParser. +* `collect` contains the intermediate characters +* of the escape sequence. `flag` is the final +* character as character code. +*/ +export interface IEscHandler { + (collect: string, flag: number): void; +} + +/** +* OSC handler signature for EscapeSequenceParser. +* `data` contains all characters right of the first ; +* example: OSC 123;foo=bar;baz\007 --> 'foo=bar;baz' +*/ +export interface IOscHandler { + (data: string): void; +} + +/** +* DCS handler signature for EscapeSequenceParser. +* EscapeSequenceParser handles DCS commands via separate +* subparsers that get hook/unhooked and can handle +* arbitrary amount of print data. +*/ +export interface IDcsHandler { + hook(collect: string, params: number[], flag: number): void; + put(data: string, start: number, end: number): void; + unhook(): void; +} + +/** +* Error handler signature for EscapeSequenceParser. +*/ +export interface IErrorHandler { + (state: IParsingState): IParsingState; +} + +/** +* EscapeSequenceParser interface. +*/ +export interface IEscapeSequenceParser { + reset(): void; + parse(data: string): void; + + setPrintHandler(callback: IPrintHandler): void; + clearPrintHandler(): void; + + setExecuteHandler(flag: string, callback: IExecuteHandler): void; + clearExecuteHandler(flag: string): void; + setExecuteHandlerFallback(callback: (...params: any[]) => void): void; + + setCsiHandler(flag: string, callback: ICsiHandler): void; + clearCsiHandler(flag: string): void; + setCsiHandlerFallback(callback: (...params: any[]) => void): void; + + setEscHandler(collect: string, flag: string, callback: IEscHandler): void; + clearEscHandler(collect: string, flag: string): void; + setEscHandlerFallback(callback: (...params: any[]) => void): void; + + setOscHandler(ident: number, callback: IOscHandler): void; + clearOscHandler(ident: number): void; + setOscHandlerFallback(callback: (...params: any[]) => void): void; + + setDcsHandler(collect: string, flag: string, handler: IDcsHandler): void; + clearDcsHandler(collect: string, flag: string): void; + setDcsHandlerFallback(handler: IDcsHandler): void; + + setErrorHandler(callback: IErrorHandler): void; + clearErrorHandler(): void; + + // remove after revamp of InputHandler methods + setPrefixHandler(callback: (collect: string) => void): void; +}