diff --git a/src/EscapeSequenceParser.test.ts_disabled b/src/EscapeSequenceParser.test.ts_disabled new file mode 100644 index 00000000..fe626290 --- /dev/null +++ b/src/EscapeSequenceParser.test.ts_disabled @@ -0,0 +1,1070 @@ +import { EscapeSequenceParser, IParserTerminal, ParserState } from './EscapeSequenceParser'; +import * as chai from 'chai'; + +function r(a: number, b: number): string[] { + let c = b - a; + let arr = new Array(c); + while (c--) { + arr[c] = String.fromCharCode(--b); + } + return arr; +} + +interface ITestTerminal extends IParserTerminal { + calls: any[]; + clear: () => void; + compare: (value: any) => void; +} + +let testTerminal: ITestTerminal = { + calls: [], + clear: function (): void { + this.calls = []; + }, + compare: function (value: any): void { + chai.expect(this.calls.slice()).eql(value); // weird bug w'o slicing here + }, + print: function (data: string, start: number, end: number): void { + this.calls.push(['print', data.substring(start, end)]); + }, + actionOSC: function (s: string): void { + this.calls.push(['osc', s]); + }, + actionExecute: function (flag: string): void { + this.calls.push(['exe', flag]); + }, + actionCSI: function (collected: string, params: number[], flag: string): void { + this.calls.push(['csi', collected, params, flag]); + }, + actionESC: function (collected: string, flag: string): void { + this.calls.push(['esc', collected, flag]); + }, + actionDCSHook: function (collected: string, params: number[], flag: string): void { + this.calls.push(['dcs hook', collected, params, flag]); + }, + actionDCSPrint: function (data: string, start: number, end: number): void { + this.calls.push(['dcs put', data.substring(start, end)]); + }, + actionDCSUnhook: function (): void { + this.calls.push(['dcs unhook']); + } +}; + +let states: number[] = [ + ParserState.GROUND, + ParserState.ESCAPE, + ParserState.ESCAPE_INTERMEDIATE, + ParserState.CSI_ENTRY, + ParserState.CSI_PARAM, + ParserState.CSI_INTERMEDIATE, + ParserState.CSI_IGNORE, + ParserState.SOS_PM_APC_STRING, + ParserState.OSC_STRING, + ParserState.DCS_ENTRY, + ParserState.DCS_PARAM, + ParserState.DCS_IGNORE, + ParserState.DCS_INTERMEDIATE, + ParserState.DCS_PASSTHROUGH +]; +let state: any; + +let parser = new EscapeSequenceParser(testTerminal); +parser.registerPrintHandler(testTerminal.print.bind(testTerminal)); + +describe('EscapeSequenceParser', function(): void { + + describe('Parser init and methods', function(): void { + it('parser init', function (): void { + let p = new EscapeSequenceParser({}); + chai.expect(p.term).a('object'); + chai.expect(p.term.actionPrint).a('function'); + chai.expect(p.term.actionOSC).a('function'); + chai.expect(p.term.actionExecute).a('function'); + chai.expect(p.term.actionCSI).a('function'); + chai.expect(p.term.actionESC).a('function'); + chai.expect(p.term.actionDCSHook).a('function'); + chai.expect(p.term.actionDCSPrint).a('function'); + chai.expect(p.term.actionDCSUnhook).a('function'); + p.parse('\x1b[31mHello World!'); + }); + it('terminal callbacks', function (): void { + chai.expect(parser.term).equal(testTerminal); + chai.expect(parser.term.actionPrint).equal(testTerminal.print); + chai.expect(parser.term.actionOSC).equal(testTerminal.actionOSC); + chai.expect(parser.term.actionExecute).equal(testTerminal.actionExecute); + chai.expect(parser.term.actionCSI).equal(testTerminal.actionCSI); + chai.expect(parser.term.actionESC).equal(testTerminal.actionESC); + chai.expect(parser.term.actionDCSHook).equal(testTerminal.actionDCSHook); + chai.expect(parser.term.actionDCSPrint).equal(testTerminal.actionDCSPrint); + chai.expect(parser.term.actionDCSUnhook).equal(testTerminal.actionDCSUnhook); + }); + it('inital states', function (): void { + chai.expect(parser.initialState).equal(0); + chai.expect(parser.currentState).equal(0); + chai.expect(parser.osc).equal(''); + chai.expect(parser.params).eql([0]); + chai.expect(parser.collected).equal(''); + }); + it('reset states', function (): void { + parser.currentState = 124; + parser.osc = '#'; + parser.params = [123]; + parser.collected = '#'; + + parser.reset(); + chai.expect(parser.currentState).equal(ParserState.GROUND); + chai.expect(parser.osc).equal(''); + chai.expect(parser.params).eql([0]); + chai.expect(parser.collected).equal(''); + }); + }); + + describe('state transitions and actions', function(): void { + it('state GROUND execute action', function (): void { + parser.reset(); + testTerminal.clear(); + let exes = r(0x00, 0x18); + exes.concat(['\x19']); + exes.concat(r(0x1c, 0x20)); + for (let i = 0; i < exes.length; ++i) { + parser.currentState = ParserState.GROUND; + parser.parse(exes[i]); + chai.expect(parser.currentState).equal(ParserState.GROUND); + testTerminal.compare([['exe', exes[i]]]); + parser.reset(); + testTerminal.clear(); + } + }); + it('state GROUND print action', function (): void { + parser.reset(); + testTerminal.clear(); + let printables = r(0x20, 0x7f); // NOTE: DEL excluded + for (let i = 0; i < printables.length; ++i) { + parser.currentState = ParserState.GROUND; + parser.parse(printables[i]); + chai.expect(parser.currentState).equal(ParserState.GROUND); + testTerminal.compare([['print', printables[i]]]); + parser.reset(); + testTerminal.clear(); + } + }); + it('trans ANYWHERE --> GROUND with actions', function (): void { + let exes = [ + '\x18', '\x1a', + '\x80', '\x81', '\x82', '\x83', '\x84', '\x85', '\x86', '\x87', '\x88', + '\x89', '\x8a', '\x8b', '\x8c', '\x8d', '\x8e', '\x8f', + '\x91', '\x92', '\x93', '\x94', '\x95', '\x96', '\x97', '\x99', '\x9a' + ]; + let exceptions = { + 8: {'\x18': [], '\x1a': []} // simply abort osc state + }; + parser.reset(); + testTerminal.clear(); + for (state in states) { + for (let i = 0; i < exes.length; ++i) { + parser.currentState = state; + parser.parse(exes[i]); + chai.expect(parser.currentState).equal(ParserState.GROUND); + testTerminal.compare(((exceptions[state]) ? exceptions[state][exes[i]] : 0) || [['exe', exes[i]]]); + parser.reset(); + testTerminal.clear(); + } + parser.parse('\x9c'); + chai.expect(parser.currentState).equal(ParserState.GROUND); + testTerminal.compare([]); + parser.reset(); + testTerminal.clear(); + } + }); + it('trans ANYWHERE --> ESCAPE with clear', function (): void { + parser.reset(); + for (state in states) { + parser.currentState = state; + parser.osc = '#'; + parser.params = [23]; + parser.collected = '#'; + parser.parse('\x1b'); + chai.expect(parser.currentState).equal(ParserState.ESCAPE); + chai.expect(parser.osc).equal(''); + chai.expect(parser.params).eql([0]); + chai.expect(parser.collected).equal(''); + parser.reset(); + } + }); + it('state ESCAPE execute rules', function (): void { + parser.reset(); + testTerminal.clear(); + let exes = r(0x00, 0x18); + exes.concat(['\x19']); + exes.concat(r(0x1c, 0x20)); + for (let i = 0; i < exes.length; ++i) { + parser.currentState = ParserState.ESCAPE; + parser.parse(exes[i]); + chai.expect(parser.currentState).equal(ParserState.ESCAPE); + testTerminal.compare([['exe', exes[i]]]); + parser.reset(); + testTerminal.clear(); + } + }); + it('state ESCAPE ignore', function (): void { + parser.reset(); + testTerminal.clear(); + parser.currentState = ParserState.ESCAPE; + parser.parse('\x7f'); + chai.expect(parser.currentState).equal(ParserState.ESCAPE); + testTerminal.compare([]); + parser.reset(); + testTerminal.clear(); + }); + it('trans ESCAPE --> GROUND with ecs_dispatch action', function (): void { + parser.reset(); + testTerminal.clear(); + let dispatches = r(0x30, 0x50); + dispatches.concat(r(0x51, 0x58)); + dispatches.concat(['\x59', '\x5a', '\x5c']); + dispatches.concat(r(0x60, 0x7f)); + for (let i = 0; i < dispatches.length; ++i) { + parser.currentState = ParserState.ESCAPE; + parser.parse(dispatches[i]); + chai.expect(parser.currentState).equal(ParserState.GROUND); + testTerminal.compare([['esc', '', dispatches[i]]]); + parser.reset(); + testTerminal.clear(); + } + }); + it('trans ESCAPE --> ESCAPE_INTERMEDIATE with collect action', function (): void { + parser.reset(); + let collect = r(0x20, 0x30); + for (let i = 0; i < collect.length; ++i) { + parser.currentState = ParserState.ESCAPE; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(ParserState.ESCAPE_INTERMEDIATE); + chai.expect(parser.collected).equal(collect[i]); + parser.reset(); + } + }); + it('state ESCAPE_INTERMEDIATE execute rules', function (): void { + parser.reset(); + testTerminal.clear(); + let exes = r(0x00, 0x18); + exes.concat(['\x19']); + exes.concat(r(0x1c, 0x20)); + for (let i = 0; i < exes.length; ++i) { + parser.currentState = ParserState.ESCAPE_INTERMEDIATE; + parser.parse(exes[i]); + chai.expect(parser.currentState).equal(ParserState.ESCAPE_INTERMEDIATE); + testTerminal.compare([['exe', exes[i]]]); + parser.reset(); + testTerminal.clear(); + } + }); + it('state ESCAPE_INTERMEDIATE ignore', function (): void { + parser.reset(); + testTerminal.clear(); + parser.currentState = ParserState.ESCAPE_INTERMEDIATE; + parser.parse('\x7f'); + chai.expect(parser.currentState).equal(ParserState.ESCAPE_INTERMEDIATE); + testTerminal.compare([]); + parser.reset(); + testTerminal.clear(); + }); + it('state ESCAPE_INTERMEDIATE collect action', function (): void { + parser.reset(); + let collect = r(0x20, 0x30); + for (let i = 0; i < collect.length; ++i) { + parser.currentState = ParserState.ESCAPE_INTERMEDIATE; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(ParserState.ESCAPE_INTERMEDIATE); + chai.expect(parser.collected).equal(collect[i]); + parser.reset(); + } + }); + it('trans ESCAPE_INTERMEDIATE --> GROUND with esc_dispatch action', function (): void { + parser.reset(); + testTerminal.clear(); + let collect = r(0x30, 0x7f); + for (let i = 0; i < collect.length; ++i) { + parser.currentState = ParserState.ESCAPE_INTERMEDIATE; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(ParserState.GROUND); + testTerminal.compare([['esc', '', collect[i]]]); + parser.reset(); + testTerminal.clear(); + } + }); + it('trans ANYWHERE/ESCAPE --> CSI_ENTRY with clear', function (): void { + parser.reset(); + // C0 + parser.currentState = ParserState.ESCAPE; + parser.osc = '#'; + parser.params = [123]; + parser.collected = '#'; + parser.parse('['); + chai.expect(parser.currentState).equal(ParserState.CSI_ENTRY); + chai.expect(parser.osc).equal(''); + chai.expect(parser.params).eql([0]); + chai.expect(parser.collected).equal(''); + parser.reset(); + // C1 + for (state in states) { + parser.currentState = state; + parser.osc = '#'; + parser.params = [123]; + parser.collected = '#'; + parser.parse('\x9b'); + chai.expect(parser.currentState).equal(ParserState.CSI_ENTRY); + chai.expect(parser.osc).equal(''); + chai.expect(parser.params).eql([0]); + chai.expect(parser.collected).equal(''); + parser.reset(); + } + }); + it('state CSI_ENTRY execute rules', function (): void { + parser.reset(); + testTerminal.clear(); + let exes = r(0x00, 0x18); + exes.concat(['\x19']); + exes.concat(r(0x1c, 0x20)); + for (let i = 0; i < exes.length; ++i) { + parser.currentState = ParserState.CSI_ENTRY; + parser.parse(exes[i]); + chai.expect(parser.currentState).equal(ParserState.CSI_ENTRY); + testTerminal.compare([['exe', exes[i]]]); + parser.reset(); + testTerminal.clear(); + } + }); + it('state CSI_ENTRY ignore', function (): void { + parser.reset(); + testTerminal.clear(); + parser.currentState = ParserState.CSI_ENTRY; + parser.parse('\x7f'); + chai.expect(parser.currentState).equal(ParserState.CSI_ENTRY); + testTerminal.compare([]); + parser.reset(); + testTerminal.clear(); + }); + it('trans CSI_ENTRY --> GROUND with csi_dispatch action', function (): void { + parser.reset(); + let dispatches = r(0x40, 0x7f); + for (let i = 0; i < dispatches.length; ++i) { + parser.currentState = ParserState.CSI_ENTRY; + parser.parse(dispatches[i]); + chai.expect(parser.currentState).equal(ParserState.GROUND); + testTerminal.compare([['csi', '', [0], dispatches[i]]]); + parser.reset(); + testTerminal.clear(); + } + }); + it('trans CSI_ENTRY --> CSI_PARAM with param/collect actions', function (): void { + parser.reset(); + let params = ['\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', '\x38', '\x39']; + let collect = ['\x3c', '\x3d', '\x3e', '\x3f']; + for (let i = 0; i < params.length; ++i) { + parser.currentState = ParserState.CSI_ENTRY; + parser.parse(params[i]); + chai.expect(parser.currentState).equal(ParserState.CSI_PARAM); + chai.expect(parser.params).eql([params[i].charCodeAt(0) - 48]); + parser.reset(); + } + parser.currentState = ParserState.CSI_ENTRY; + parser.parse('\x3b'); + chai.expect(parser.currentState).equal(ParserState.CSI_PARAM); + chai.expect(parser.params).eql([0, 0]); + parser.reset(); + for (let i = 0; i < collect.length; ++i) { + parser.currentState = ParserState.CSI_ENTRY; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(ParserState.CSI_PARAM); + chai.expect(parser.collected).equal(collect[i]); + parser.reset(); + } + }); + it('state CSI_PARAM execute rules', function (): void { + parser.reset(); + testTerminal.clear(); + let exes = r(0x00, 0x18); + exes.concat(['\x19']); + exes.concat(r(0x1c, 0x20)); + for (let i = 0; i < exes.length; ++i) { + parser.currentState = ParserState.CSI_PARAM; + parser.parse(exes[i]); + chai.expect(parser.currentState).equal(ParserState.CSI_PARAM); + testTerminal.compare([['exe', exes[i]]]); + parser.reset(); + testTerminal.clear(); + } + }); + it('state CSI_PARAM param action', function (): void { + parser.reset(); + let params = ['\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', '\x38', '\x39']; + for (let i = 0; i < params.length; ++i) { + parser.currentState = ParserState.CSI_PARAM; + parser.parse(params[i]); + chai.expect(parser.currentState).equal(ParserState.CSI_PARAM); + chai.expect(parser.params).eql([params[i].charCodeAt(0) - 48]); + parser.reset(); + } + parser.currentState = ParserState.CSI_PARAM; + parser.parse('\x3b'); + chai.expect(parser.currentState).equal(ParserState.CSI_PARAM); + chai.expect(parser.params).eql([0, 0]); + parser.reset(); + }); + it('state CSI_PARAM ignore', function (): void { + parser.reset(); + testTerminal.clear(); + parser.currentState = ParserState.CSI_PARAM; + parser.parse('\x7f'); + chai.expect(parser.currentState).equal(ParserState.CSI_PARAM); + testTerminal.compare([]); + parser.reset(); + testTerminal.clear(); + }); + it('trans CSI_PARAM --> GROUND with csi_dispatch action', function (): void { + parser.reset(); + let dispatches = r(0x40, 0x7f); + for (let i = 0; i < dispatches.length; ++i) { + parser.currentState = ParserState.CSI_PARAM; + parser.params = [0, 1]; + parser.parse(dispatches[i]); + chai.expect(parser.currentState).equal(ParserState.GROUND); + testTerminal.compare([['csi', '', [0, 1], dispatches[i]]]); + parser.reset(); + testTerminal.clear(); + } + }); + it('trans CSI_ENTRY --> CSI_INTERMEDIATE with collect action', function (): void { + parser.reset(); + let collect = r(0x20, 0x30); + for (let i = 0; i < collect.length; ++i) { + parser.currentState = ParserState.CSI_ENTRY; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(ParserState.CSI_INTERMEDIATE); + chai.expect(parser.collected).equal(collect[i]); + parser.reset(); + } + }); + it('trans CSI_PARAM --> CSI_INTERMEDIATE with collect action', function (): void { + parser.reset(); + let collect = r(0x20, 0x30); + for (let i = 0; i < collect.length; ++i) { + parser.currentState = ParserState.CSI_PARAM; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(ParserState.CSI_INTERMEDIATE); + chai.expect(parser.collected).equal(collect[i]); + parser.reset(); + } + }); + it('state CSI_INTERMEDIATE execute rules', function (): void { + parser.reset(); + testTerminal.clear(); + let exes = r(0x00, 0x18); + exes.concat(['\x19']); + exes.concat(r(0x1c, 0x20)); + for (let i = 0; i < exes.length; ++i) { + parser.currentState = ParserState.CSI_INTERMEDIATE; + parser.parse(exes[i]); + chai.expect(parser.currentState).equal(ParserState.CSI_INTERMEDIATE); + testTerminal.compare([['exe', exes[i]]]); + parser.reset(); + testTerminal.clear(); + } + }); + it('state CSI_INTERMEDIATE collect', function (): void { + parser.reset(); + let collect = r(0x20, 0x30); + for (let i = 0; i < collect.length; ++i) { + parser.currentState = ParserState.CSI_INTERMEDIATE; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(ParserState.CSI_INTERMEDIATE); + chai.expect(parser.collected).equal(collect[i]); + parser.reset(); + } + }); + it('state CSI_INTERMEDIATE ignore', function (): void { + parser.reset(); + testTerminal.clear(); + parser.currentState = ParserState.CSI_INTERMEDIATE; + parser.parse('\x7f'); + chai.expect(parser.currentState).equal(ParserState.CSI_INTERMEDIATE); + testTerminal.compare([]); + parser.reset(); + testTerminal.clear(); + }); + it('trans CSI_INTERMEDIATE --> GROUND with csi_dispatch action', function (): void { + parser.reset(); + let dispatches = r(0x40, 0x7f); + for (let i = 0; i < dispatches.length; ++i) { + parser.currentState = ParserState.CSI_INTERMEDIATE; + parser.params = [0, 1]; + parser.parse(dispatches[i]); + chai.expect(parser.currentState).equal(ParserState.GROUND); + testTerminal.compare([['csi', '', [0, 1], dispatches[i]]]); + parser.reset(); + testTerminal.clear(); + } + }); + it('trans CSI_ENTRY --> CSI_IGNORE', function (): void { + parser.reset(); + parser.currentState = ParserState.CSI_ENTRY; + parser.parse('\x3a'); + chai.expect(parser.currentState).equal(ParserState.CSI_IGNORE); + parser.reset(); + }); + it('trans CSI_PARAM --> CSI_IGNORE', function (): void { + parser.reset(); + let chars = ['\x3a', '\x3c', '\x3d', '\x3e', '\x3f']; + for (let i = 0; i < chars.length; ++i) { + parser.currentState = ParserState.CSI_PARAM; + parser.parse('\x3b' + chars[i]); + chai.expect(parser.currentState).equal(ParserState.CSI_IGNORE); + chai.expect(parser.params).eql([0, 0]); + parser.reset(); + } + }); + it('trans CSI_INTERMEDIATE --> CSI_IGNORE', function (): void { + parser.reset(); + let chars = r(0x30, 0x40); + for (let i = 0; i < chars.length; ++i) { + parser.currentState = ParserState.CSI_INTERMEDIATE; + parser.parse(chars[i]); + chai.expect(parser.currentState).equal(ParserState.CSI_IGNORE); + chai.expect(parser.params).eql([0]); + parser.reset(); + } + }); + it('state CSI_IGNORE execute rules', function (): void { + parser.reset(); + testTerminal.clear(); + let exes = r(0x00, 0x18); + exes.concat(['\x19']); + exes.concat(r(0x1c, 0x20)); + for (let i = 0; i < exes.length; ++i) { + parser.currentState = ParserState.CSI_IGNORE; + parser.parse(exes[i]); + chai.expect(parser.currentState).equal(ParserState.CSI_IGNORE); + testTerminal.compare([['exe', exes[i]]]); + parser.reset(); + testTerminal.clear(); + } + }); + it('state CSI_IGNORE ignore', function (): void { + parser.reset(); + testTerminal.clear(); + let ignored = r(0x20, 0x40); + ignored.concat(['\x7f']); + for (let i = 0; i < ignored.length; ++i) { + parser.currentState = ParserState.CSI_IGNORE; + parser.parse(ignored[i]); + chai.expect(parser.currentState).equal(ParserState.CSI_IGNORE); + testTerminal.compare([]); + parser.reset(); + testTerminal.clear(); + } + }); + it('trans CSI_IGNORE --> GROUND', function (): void { + parser.reset(); + let dispatches = r(0x40, 0x7f); + for (let i = 0; i < dispatches.length; ++i) { + parser.currentState = ParserState.CSI_IGNORE; + parser.params = [0, 1]; + parser.parse(dispatches[i]); + chai.expect(parser.currentState).equal(ParserState.GROUND); + testTerminal.compare([]); + parser.reset(); + testTerminal.clear(); + } + }); + it('trans ANYWHERE/ESCAPE --> SOS_PM_APC_STRING', function (): void { + parser.reset(); + // C0 + let initializers = ['\x58', '\x5e', '\x5f']; + for (let i = 0; i < initializers.length; ++i) { + parser.parse('\x1b' + initializers[i]); + chai.expect(parser.currentState).equal(ParserState.SOS_PM_APC_STRING); + parser.reset(); + } + // C1 + for (state in states) { + parser.currentState = state; + initializers = ['\x98', '\x9e', '\x9f']; + for (let i = 0; i < initializers.length; ++i) { + parser.parse(initializers[i]); + chai.expect(parser.currentState).equal(ParserState.SOS_PM_APC_STRING); + parser.reset(); + } + } + }); + it('state SOS_PM_APC_STRING ignore rules', function (): void { + parser.reset(); + let ignored = r(0x00, 0x18); + ignored.concat(['\x19']); + ignored.concat(r(0x1c, 0x20)); + ignored.concat(r(0x20, 0x80)); + for (let i = 0; i < ignored.length; ++i) { + parser.currentState = ParserState.SOS_PM_APC_STRING; + parser.parse(ignored[i]); + chai.expect(parser.currentState).equal(ParserState.SOS_PM_APC_STRING); + parser.reset(); + } + }); + it('trans ANYWHERE/ESCAPE --> OSC_STRING', function (): void { + parser.reset(); + // C0 + parser.parse('\x1b]'); + chai.expect(parser.currentState).equal(ParserState.OSC_STRING); + parser.reset(); + // C1 + for (state in states) { + parser.currentState = state; + parser.parse('\x9d'); + chai.expect(parser.currentState).equal(ParserState.OSC_STRING); + parser.reset(); + } + }); + it('state OSC_STRING ignore rules', function (): void { + parser.reset(); + let ignored = [ + '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', /*'\x07',*/ '\x08', + '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f', '\x10', '\x11', + '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x19', '\x1c', '\x1d', '\x1e', '\x1f']; + for (let i = 0; i < ignored.length; ++i) { + parser.currentState = ParserState.OSC_STRING; + parser.parse(ignored[i]); + chai.expect(parser.currentState).equal(ParserState.OSC_STRING); + chai.expect(parser.osc).equal(''); + parser.reset(); + } + }); + it('state OSC_STRING put action', function (): void { + parser.reset(); + let puts = r(0x20, 0x80); + for (let i = 0; i < puts.length; ++i) { + parser.currentState = ParserState.OSC_STRING; + parser.parse(puts[i]); + chai.expect(parser.currentState).equal(ParserState.OSC_STRING); + chai.expect(parser.osc).equal(puts[i]); + parser.reset(); + } + }); + it('state DCS_ENTRY', function (): void { + parser.reset(); + // C0 + parser.parse('\x1bP'); + chai.expect(parser.currentState).equal(ParserState.DCS_ENTRY); + parser.reset(); + // C1 + for (state in states) { + parser.currentState = state; + parser.parse('\x90'); + chai.expect(parser.currentState).equal(ParserState.DCS_ENTRY); + parser.reset(); + } + }); + it('state DCS_ENTRY ignore rules', function (): void { + parser.reset(); + let ignored = [ + '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', '\x08', + '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f', '\x10', '\x11', + '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x19', '\x1c', '\x1d', '\x1e', '\x1f', '\x7f']; + for (let i = 0; i < ignored.length; ++i) { + parser.currentState = ParserState.DCS_ENTRY; + parser.parse(ignored[i]); + chai.expect(parser.currentState).equal(ParserState.DCS_ENTRY); + parser.reset(); + } + }); + it('state DCS_ENTRY --> DCS_PARAM with param/collect actions', function (): void { + parser.reset(); + let params = ['\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', '\x38', '\x39']; + let collect = ['\x3c', '\x3d', '\x3e', '\x3f']; + for (let i = 0; i < params.length; ++i) { + parser.currentState = ParserState.DCS_ENTRY; + parser.parse(params[i]); + chai.expect(parser.currentState).equal(ParserState.DCS_PARAM); + chai.expect(parser.params).eql([params[i].charCodeAt(0) - 48]); + parser.reset(); + } + parser.currentState = ParserState.DCS_ENTRY; + parser.parse('\x3b'); + chai.expect(parser.currentState).equal(ParserState.DCS_PARAM); + chai.expect(parser.params).eql([0, 0]); + parser.reset(); + for (let i = 0; i < collect.length; ++i) { + parser.currentState = ParserState.DCS_ENTRY; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(ParserState.DCS_PARAM); + chai.expect(parser.collected).equal(collect[i]); + parser.reset(); + } + }); + it('state DCS_PARAM ignore rules', function (): void { + parser.reset(); + let ignored = [ + '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', '\x08', + '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f', '\x10', '\x11', + '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x19', '\x1c', '\x1d', '\x1e', '\x1f', '\x7f']; + for (let i = 0; i < ignored.length; ++i) { + parser.currentState = ParserState.DCS_PARAM; + parser.parse(ignored[i]); + chai.expect(parser.currentState).equal(ParserState.DCS_PARAM); + parser.reset(); + } + }); + it('state DCS_PARAM param action', function (): void { + parser.reset(); + let params = ['\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', '\x38', '\x39']; + for (let i = 0; i < params.length; ++i) { + parser.currentState = ParserState.DCS_PARAM; + parser.parse(params[i]); + chai.expect(parser.currentState).equal(ParserState.DCS_PARAM); + chai.expect(parser.params).eql([params[i].charCodeAt(0) - 48]); + parser.reset(); + } + parser.currentState = ParserState.DCS_PARAM; + parser.parse('\x3b'); + chai.expect(parser.currentState).equal(ParserState.DCS_PARAM); + chai.expect(parser.params).eql([0, 0]); + parser.reset(); + }); + it('trans DCS_ENTRY --> DCS_IGNORE', function (): void { + parser.reset(); + parser.currentState = ParserState.DCS_ENTRY; + parser.parse('\x3a'); + chai.expect(parser.currentState).equal(ParserState.DCS_IGNORE); + parser.reset(); + }); + it('trans DCS_PARAM --> DCS_IGNORE', function (): void { + parser.reset(); + let chars = ['\x3a', '\x3c', '\x3d', '\x3e', '\x3f']; + for (let i = 0; i < chars.length; ++i) { + parser.currentState = ParserState.DCS_PARAM; + parser.parse('\x3b' + chars[i]); + chai.expect(parser.currentState).equal(ParserState.DCS_IGNORE); + chai.expect(parser.params).eql([0, 0]); + parser.reset(); + } + }); + it('trans DCS_INTERMEDIATE --> DCS_IGNORE', function (): void { + parser.reset(); + let chars = r(0x30, 0x40); + for (let i = 0; i < chars.length; ++i) { + parser.currentState = ParserState.DCS_INTERMEDIATE; + parser.parse(chars[i]); + chai.expect(parser.currentState).equal(ParserState.DCS_IGNORE); + parser.reset(); + } + }); + it('state DCS_IGNORE ignore rules', function (): void { + parser.reset(); + let ignored = [ + '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', '\x08', + '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f', '\x10', '\x11', + '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x19', '\x1c', '\x1d', '\x1e', '\x1f', '\x7f']; + ignored.concat(r(0x20, 0x80)); + for (let i = 0; i < ignored.length; ++i) { + parser.currentState = ParserState.DCS_IGNORE; + parser.parse(ignored[i]); + chai.expect(parser.currentState).equal(ParserState.DCS_IGNORE); + parser.reset(); + } + }); + it('trans DCS_ENTRY --> DCS_INTERMEDIATE with collect action', function (): void { + parser.reset(); + let collect = r(0x20, 0x30); + for (let i = 0; i < collect.length; ++i) { + parser.currentState = ParserState.DCS_ENTRY; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(ParserState.DCS_INTERMEDIATE); + chai.expect(parser.collected).equal(collect[i]); + parser.reset(); + } + }); + it('trans DCS_PARAM --> DCS_INTERMEDIATE with collect action', function (): void { + parser.reset(); + let collect = r(0x20, 0x30); + for (let i = 0; i < collect.length; ++i) { + parser.currentState = ParserState.DCS_PARAM; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(ParserState.DCS_INTERMEDIATE); + chai.expect(parser.collected).equal(collect[i]); + parser.reset(); + } + }); + it('state DCS_INTERMEDIATE ignore rules', function (): void { + parser.reset(); + let ignored = [ + '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', '\x08', + '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f', '\x10', '\x11', + '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x19', '\x1c', '\x1d', '\x1e', '\x1f', '\x7f']; + for (let i = 0; i < ignored.length; ++i) { + parser.currentState = ParserState.DCS_INTERMEDIATE; + parser.parse(ignored[i]); + chai.expect(parser.currentState).equal(ParserState.DCS_INTERMEDIATE); + parser.reset(); + } + }); + it('state DCS_INTERMEDIATE collect action', function (): void { + parser.reset(); + let collect = r(0x20, 0x30); + for (let i = 0; i < collect.length; ++i) { + parser.currentState = ParserState.DCS_INTERMEDIATE; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(ParserState.DCS_INTERMEDIATE); + chai.expect(parser.collected).equal(collect[i]); + parser.reset(); + } + }); + it('trans DCS_INTERMEDIATE --> DCS_IGNORE', function (): void { + parser.reset(); + let chars = r(0x30, 0x40); + for (let i = 0; i < chars.length; ++i) { + parser.currentState = ParserState.DCS_INTERMEDIATE; + parser.parse('\x20' + chars[i]); + chai.expect(parser.currentState).equal(ParserState.DCS_IGNORE); + chai.expect(parser.collected).equal('\x20'); + parser.reset(); + } + }); + it('trans DCS_ENTRY --> DCS_PASSTHROUGH with hook', function (): void { + parser.reset(); + testTerminal.clear(); + let collect = r(0x40, 0x7f); + for (let i = 0; i < collect.length; ++i) { + parser.currentState = ParserState.DCS_ENTRY; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(ParserState.DCS_PASSTHROUGH); + testTerminal.compare([['dcs hook', '', [0], collect[i]]]); + parser.reset(); + testTerminal.clear(); + } + }); + it('trans DCS_PARAM --> DCS_PASSTHROUGH with hook', function (): void { + parser.reset(); + testTerminal.clear(); + let collect = r(0x40, 0x7f); + for (let i = 0; i < collect.length; ++i) { + parser.currentState = ParserState.DCS_PARAM; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(ParserState.DCS_PASSTHROUGH); + testTerminal.compare([['dcs hook', '', [0], collect[i]]]); + parser.reset(); + testTerminal.clear(); + } + }); + it('trans DCS_INTERMEDIATE --> DCS_PASSTHROUGH with hook', function (): void { + parser.reset(); + testTerminal.clear(); + let collect = r(0x40, 0x7f); + for (let i = 0; i < collect.length; ++i) { + parser.currentState = ParserState.DCS_INTERMEDIATE; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(ParserState.DCS_PASSTHROUGH); + testTerminal.compare([['dcs hook', '', [0], collect[i]]]); + parser.reset(); + testTerminal.clear(); + } + }); + it('state DCS_PASSTHROUGH put action', function (): void { + parser.reset(); + testTerminal.clear(); + let puts = r(0x00, 0x18); + puts.concat(['\x19']); + puts.concat(r(0x1c, 0x20)); + puts.concat(r(0x20, 0x7f)); + for (let i = 0; i < puts.length; ++i) { + parser.currentState = ParserState.DCS_PASSTHROUGH; + parser.parse(puts[i]); + chai.expect(parser.currentState).equal(ParserState.DCS_PASSTHROUGH); + testTerminal.compare([['dcs put', puts[i]]]); + parser.reset(); + testTerminal.clear(); + } + }); + it('state DCS_PASSTHROUGH ignore', function (): void { + parser.reset(); + testTerminal.clear(); + parser.currentState = ParserState.DCS_PASSTHROUGH; + parser.parse('\x7f'); + chai.expect(parser.currentState).equal(ParserState.DCS_PASSTHROUGH); + testTerminal.compare([]); + parser.reset(); + testTerminal.clear(); + }); + }); + + function test(s: string, value: any, noReset: any): void { + if (!noReset) { + parser.reset(); + testTerminal.clear(); + } + parser.parse(s); + testTerminal.compare(value); + } + + describe('escape sequence examples', function(): void { + it('CSI with print and execute', function (): void { + test('\x1b[<31;5mHello World! öäü€\nabc', + [ + ['csi', '<', [31, 5], 'm'], + ['print', 'Hello World! öäü€'], + ['exe', '\n'], + ['print', 'abc'] + ], null); + }); + it('OSC', function (): void { + test('\x1b]0;abc123€öäü\x07', [ + ['osc', '0;abc123€öäü'] + ], null); + }); + it('single DCS', function (): void { + test('\x1bP1;2;3+$abc;de\x9c', [ + ['dcs hook', '+$', [1, 2, 3], 'a'], + ['dcs put', 'bc;de'], + ['dcs unhook'] + ], null); + }); + it('multi DCS', function (): void { + test('\x1bP1;2;3+$abc;de', [ + ['dcs hook', '+$', [1, 2, 3], 'a'], + ['dcs put', 'bc;de'] + ], null); + testTerminal.clear(); + test('abc\x9c', [ + ['dcs put', 'abc'], + ['dcs unhook'] + ], true); + }); + it('print + DCS(C1)', function (): void { + test('abc\x901;2;3+$abc;de\x9c', [ + ['print', 'abc'], + ['dcs hook', '+$', [1, 2, 3], 'a'], + ['dcs put', 'bc;de'], + ['dcs unhook'] + ], null); + }); + it('print + PM(C1) + print', function (): void { + test('abc\x98123tzf\x9cdefg', [ + ['print', 'abc'], + ['print', 'defg'] + ], null); + }); + it('print + OSC(C1) + print', function (): void { + test('abc\x9d123tzf\x9cdefg', [ + ['print', 'abc'], + ['osc', '123tzf'], + ['print', 'defg'] + ], null); + }); + it('error recovery', function (): void { + test('\x1b[1€abcdefg\x9b<;c', [ + ['print', 'abcdefg'], + ['csi', '<', [0, 0], 'c'] + ], null); + }); + }); + + describe('coverage tests', function(): void { + it('CSI_IGNORE error', function (): void { + parser.reset(); + testTerminal.clear(); + parser.currentState = ParserState.CSI_IGNORE; + parser.parse('€öäü'); + chai.expect(parser.currentState).equal(ParserState.CSI_IGNORE); + testTerminal.compare([]); + parser.reset(); + testTerminal.clear(); + }); + it('DCS_IGNORE error', function (): void { + parser.reset(); + testTerminal.clear(); + parser.currentState = ParserState.DCS_IGNORE; + parser.parse('€öäü'); + chai.expect(parser.currentState).equal(ParserState.DCS_IGNORE); + testTerminal.compare([]); + parser.reset(); + testTerminal.clear(); + }); + it('DCS_PASSTHROUGH error', function (): void { + parser.reset(); + testTerminal.clear(); + parser.currentState = ParserState.DCS_PASSTHROUGH; + parser.parse('€öäü'); + chai.expect(parser.currentState).equal(ParserState.DCS_PASSTHROUGH); + testTerminal.compare([['dcs put', '€öäü']]); + parser.reset(); + testTerminal.clear(); + }); + it('error else of if (code > 159)', function (): void { + parser.reset(); + testTerminal.clear(); + parser.currentState = ParserState.GROUND; + parser.parse('\x1e'); + chai.expect(parser.currentState).equal(ParserState.GROUND); + testTerminal.compare([]); + parser.reset(); + testTerminal.clear(); + }); + }); + + /* + let errorTerminal1 = function(): void {}; + errorTerminal1.prototype = testTerminal; + let errTerminal1 = new errorTerminal1(); + errTerminal1.actionError = function(e: any): void { + this.calls.push(['error', e]); + }; + let errParser1 = new EscapeSequenceParser(errTerminal1); + errParser1.registerPrintHandler(testTerminal.actionPrint.bind(errorTerminal1)); + + let errorTerminal2 = function(): void {}; + errorTerminal2.prototype = testTerminal; + let errTerminal2 = new errorTerminal2(); + errTerminal2.actionError = function(e: any): any { + this.calls.push(['error', e]); + return true; // --> abort parsing + }; + let errParser2 = new EscapeSequenceParser(errTerminal2); + errParser2.registerPrintHandler(testTerminal.actionPrint.bind(errorTerminal2)); + + describe('error tests', function(): void { + it('CSI_PARAM unicode error - actionError output w/o abort', function (): void { + errParser1.parse('\x1b[<31;5€normal print'); + errTerminal1.compare([ + ['error', { + abort: false, + position: 7, + code: '€'.charCodeAt(0), + currentState: 4, + print: -1, + dcs: -1, + osc: '', + collected: '<', + params: [31, 5]}], + ['print', 'normal print'] + ]); + parser.reset(); + testTerminal.clear(); + }); + it('CSI_PARAM unicode error - actionError output with abort', function (): void { + errParser2.parse('\x1b[<31;5€no print'); + errTerminal2.compare([ + ['error', { + abort: false, + position: 7, + code: '€'.charCodeAt(0), + currentState: 4, + print: -1, + dcs: -1, + osc: '', + collected: '<', + params: [31, 5]}] + ]); + parser.reset(); + testTerminal.clear(); + }); + }); + */ + +}); diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts index 592c548b..59aff250 100644 --- a/src/EscapeSequenceParser.ts +++ b/src/EscapeSequenceParser.ts @@ -5,7 +5,7 @@ import { C0 } from './EscapeSequences'; // terminal interface for the escape sequence parser export interface IParserTerminal { - actionPrint?: (data: string, start: number, end: number) => void; + print?: (data: string, start: number, end: number) => void; actionOSC?: (data: string) => void; actionExecute?: (flag: string) => void; actionCSI?: (collected: string, params: number[], flag: string) => void; @@ -28,6 +28,18 @@ export interface IParsingState { 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 IDcsHandler { hook(collect: string): void; put(data: string): void; @@ -37,20 +49,30 @@ export interface IDcsHandler { export interface IEscapeSequenceParser { reset(): void; parse(data: string): void; - registerPrintHandler(callback: (data: string, start: number, end: number) => void): void; - registerExecuteHandler(flag: number, callback: () => void): void; - registerCsiHandler(flag: number, callback: (params: number[], collect: string) => void): void; - registerEscHandler(flag: number, callback: (collect: string) => void): void; - registerOscHandler(flag: number, callback: (data: string) => void): void; - registerDcsHandler(flag: number, handler: IDcsHandler): void; - registerErrorHandler(callback: (state: IParsingState) => IParsingState): void; + + registerPrintHandler(callback: IPrintHandler): void; deregisterPrintHandler(callback: (data: string) => void): void; - deregisterExecuteHandler(flag: number, callback: () => void): void; - deregisterCsiHandler(flag: number, callback: (params: number[], collect: string) => void): void; - deregisterEscHandler(flag: number, callback: (collect: string) => void): void; - deregisterOscHandler(flag: number, callback: (data: string) => void): void; - deregisterDcsHandler(flag: number, handler: IDcsHandler): void; + + registerExecuteHandler(flag: string, callback: IExecuteHandler): void; + deregisterExecuteHandler(flag: string, callback: () => void): void; + + registerCsiHandler(flag: string, callback: ICsiHandler): void; + deregisterCsiHandler(flag: string, callback: (params: number[], collect: string) => void): void; + + registerEscHandler(collect: string, flag: string, callback: (flag: string) => void): void; + deregisterEscHandler(collect: string, flag: string, callback: (collect: string) => void): void; + + registerOscHandler(ident: number, callback: (data: string) => void): void; + deregisterOscHandler(ident: number, callback: (data: string) => void): void; + + registerDcsHandler(collect: string, flag: string, handler: IDcsHandler): void; + deregisterDcsHandler(collect: string, flag: string, handler: IDcsHandler): void; + + registerErrorHandler(callback: (state: IParsingState) => IParsingState): void; deregisterErrorHandler(callback: (state: IParsingState) => IParsingState): void; + + // remove after revamp of InputHandler methods + registerPrefixHandler(callback: (collect: string) => void): void; } @@ -265,8 +287,15 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { public collected: string; public term: any; - private _printHandler: (data: string, start: number, end: number) => void; - private _csiHandlers: Function[]; + private _printHandler: IPrintHandler; + private _executeHandlers: any; + private _csiHandlers: any; + private _escHandlers: any; + private _oscHandlers: any; + private _dcsHandlers: any; + + // FIXME: to be removed + private _tempPrefixHandler: any; constructor( terminal?: IParserTerminal | any, @@ -287,32 +316,54 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { this.term[instructions[i]] = function(): void {}; } } - this._printHandler = function(): void {}; - - this._csiHandlers = []; - for (let i=0; i<256; ++i) - this._csiHandlers.push(function(): void {}); + this._printHandler = (data, start, end): void => {}; + 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); } - registerPrintHandler(callback: (data: string, start: number, end: number) => void): void { + registerPrintHandler(callback: IPrintHandler): void { this._printHandler = callback; } - registerExecuteHandler(flag: number, callback: () => void): void {} - registerCsiHandler(flag: number, callback: (params: number[], collect: string) => void): void { - this._csiHandlers[flag] = callback; - } - registerEscHandler(flag: number, callback: (collect: string) => void): void {} - registerOscHandler(flag: number, callback: (data: string) => void): void {} - registerDcsHandler(flag: number, handler: IDcsHandler): void {} - registerErrorHandler(callback: (state: IParsingState) => IParsingState): void {} deregisterPrintHandler(callback: (data: string) => void): void {} - deregisterExecuteHandler(flag: number, callback: () => void): void {} - deregisterCsiHandler(flag: number, callback: (params: number[], collect: string) => void): void {} - deregisterEscHandler(flag: number, callback: (collect: string) => void): void {} - deregisterOscHandler(flag: number, callback: (data: string) => void): void {} - deregisterDcsHandler(flag: number, handler: IDcsHandler): void {} + + registerExecuteHandler(flag: string, callback: IExecuteHandler): void { + this._executeHandlers[flag.charCodeAt(0)] = callback; + } + deregisterExecuteHandler(flag: string, callback: () => void): void {} + + registerCsiHandler(flag: string, callback: ICsiHandler): void { + this._csiHandlers[flag.charCodeAt(0)] = callback; + } + deregisterCsiHandler(flag: string, callback: (params: number[], collect: string) => void): void {} + + registerEscHandler(collect: string, flag: string, callback: (collect: string) => void): void { + this._escHandlers[collect + flag] = callback; + } + deregisterEscHandler(collect: string, flag: string, callback: (collect: string) => void): void {} + + registerOscHandler(ident: number, callback: (data: string) => void): void { + this._oscHandlers[ident] = callback; + } + deregisterOscHandler(ident: number, callback: (data: string) => void): void {} + + registerDcsHandler(collect: string, flag: string, handler: IDcsHandler): void { + this._dcsHandlers[collect + flag] = handler; + } + deregisterDcsHandler(collect: string, flag: string, handler: IDcsHandler): void {} + + registerErrorHandler(callback: (state: IParsingState) => IParsingState): void { + + } deregisterErrorHandler(callback: (state: IParsingState) => IParsingState): void {} + // FIXME: to be removed + registerPrefixHandler(callback: (collect: string) => void): void { + this._tempPrefixHandler = callback; + } + reset(): void { this.currentState = this.initialState; this.osc = ''; @@ -362,7 +413,8 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { this._printHandler(data, print, i); print = -1; } - this.term.actionExecute(String.fromCharCode(code)); + if (this._executeHandlers[code]) this._executeHandlers[code](); + else console.log('unhandled EXEC %s', code); // FIXME: set some default action break; case ParserAction.IGNORE: // handle leftover print or dcs chars @@ -423,8 +475,9 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { } break; case ParserAction.CSI_DISPATCH: - // this.term.actionCSI(collected, params, String.fromCharCode(code)); - this._csiHandlers[code](params, collected); + this._tempPrefixHandler(collected); // FIXME: to be removed + if (this._csiHandlers[code]) this._csiHandlers[code](params, collected); + else console.log('unhandled CSI %s %s %s', code, params, collected); // FIXME: set some default action break; case ParserAction.PARAM: if (code === 0x3b) params.push(0); @@ -434,7 +487,9 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { collected += String.fromCharCode(code); break; case ParserAction.ESC_DISPATCH: - this.term.actionESC(collected, String.fromCharCode(code)); + let ident = collected + String.fromCharCode(code); + if (this._escHandlers[ident]) this._escHandlers[ident](params, collected); + else console.log('unhandled ESC %s %s', collected, String.fromCharCode(code)); // FIXME: set some default action break; case ParserAction.CLEAR: if (~print) { @@ -472,7 +527,13 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { osc += data.charAt(i); break; case ParserAction.OSC_END: - if (osc && code !== 0x18 && code !== 0x1a) this.term.actionOSC(osc); + if (osc && code !== 0x18 && code !== 0x1a) { + let idx = osc.indexOf(';'); + let identifier = parseInt(osc.substring(0, idx)); + let content = osc.substring(idx + 1); + if (this._oscHandlers[identifier]) this._oscHandlers[identifier](content); + else console.log('unhandled OSC %s %s', identifier, content); // FIXME: set some default action + } if (code === 0x1b) transition |= ParserState.ESCAPE; osc = ''; params = [0]; @@ -502,9 +563,7 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from './Buffer'; import { wcwidth } from './CharWidth'; -// glue code between AnsiParser and Terminal -// action methods are the places to call custom sequence handlers -// Q: Do we need custom handler support for all escape sequences types? + // Q: Merge class with InputHandler? export class ParserTerminal implements IParserTerminal { private _parser: EscapeSequenceParser; @@ -516,60 +575,168 @@ export class ParserTerminal implements IParserTerminal { this._terminal = _terminal; this._inputHandler = _inputHandler; - this._parser.registerPrintHandler(this.actionPrint.bind(this)); - this._parser.registerCsiHandler('@'.charCodeAt(0), this._inputHandler.insertChars.bind(this._inputHandler)); - this._parser.registerCsiHandler('A'.charCodeAt(0), this._inputHandler.cursorUp.bind(this._inputHandler)); - this._parser.registerCsiHandler('B'.charCodeAt(0), this._inputHandler.cursorDown.bind(this._inputHandler)); - this._parser.registerCsiHandler('C'.charCodeAt(0), this._inputHandler.cursorForward.bind(this._inputHandler)); - this._parser.registerCsiHandler('D'.charCodeAt(0), this._inputHandler.cursorBackward.bind(this._inputHandler)); - this._parser.registerCsiHandler('E'.charCodeAt(0), this._inputHandler.cursorNextLine.bind(this._inputHandler)); - this._parser.registerCsiHandler('F'.charCodeAt(0), this._inputHandler.cursorPrecedingLine.bind(this._inputHandler)); - this._parser.registerCsiHandler('G'.charCodeAt(0), this._inputHandler.cursorCharAbsolute.bind(this._inputHandler)); - this._parser.registerCsiHandler('H'.charCodeAt(0), this._inputHandler.cursorPosition.bind(this._inputHandler)); - this._parser.registerCsiHandler('I'.charCodeAt(0), this._inputHandler.cursorForwardTab.bind(this._inputHandler)); - this._parser.registerCsiHandler('J'.charCodeAt(0), this._inputHandler.eraseInDisplay.bind(this._inputHandler)); - this._parser.registerCsiHandler('K'.charCodeAt(0), this._inputHandler.eraseInLine.bind(this._inputHandler)); - this._parser.registerCsiHandler('L'.charCodeAt(0), this._inputHandler.insertLines.bind(this._inputHandler)); - this._parser.registerCsiHandler('M'.charCodeAt(0), this._inputHandler.deleteLines.bind(this._inputHandler)); - this._parser.registerCsiHandler('P'.charCodeAt(0), this._inputHandler.deleteChars.bind(this._inputHandler)); - this._parser.registerCsiHandler('S'.charCodeAt(0), this._inputHandler.scrollUp.bind(this._inputHandler)); - this._parser.registerCsiHandler('T'.charCodeAt(0), + // FIXME: remove temporary fix to get collect to terminal + this._parser.registerPrefixHandler((collect: string) => { this._terminal.prefix = collect; }); + + // print handler + this._parser.registerPrintHandler(this.print.bind(this)); + + // CSI handler + this._parser.registerCsiHandler('@', this._inputHandler.insertChars.bind(this._inputHandler)); + this._parser.registerCsiHandler('A', this._inputHandler.cursorUp.bind(this._inputHandler)); + this._parser.registerCsiHandler('B', this._inputHandler.cursorDown.bind(this._inputHandler)); + this._parser.registerCsiHandler('C', this._inputHandler.cursorForward.bind(this._inputHandler)); + this._parser.registerCsiHandler('D', this._inputHandler.cursorBackward.bind(this._inputHandler)); + this._parser.registerCsiHandler('E', this._inputHandler.cursorNextLine.bind(this._inputHandler)); + this._parser.registerCsiHandler('F', this._inputHandler.cursorPrecedingLine.bind(this._inputHandler)); + this._parser.registerCsiHandler('G', this._inputHandler.cursorCharAbsolute.bind(this._inputHandler)); + this._parser.registerCsiHandler('H', this._inputHandler.cursorPosition.bind(this._inputHandler)); + this._parser.registerCsiHandler('I', this._inputHandler.cursorForwardTab.bind(this._inputHandler)); + this._parser.registerCsiHandler('J', this._inputHandler.eraseInDisplay.bind(this._inputHandler)); + this._parser.registerCsiHandler('K', this._inputHandler.eraseInLine.bind(this._inputHandler)); + this._parser.registerCsiHandler('L', this._inputHandler.insertLines.bind(this._inputHandler)); + this._parser.registerCsiHandler('M', this._inputHandler.deleteLines.bind(this._inputHandler)); + this._parser.registerCsiHandler('P', this._inputHandler.deleteChars.bind(this._inputHandler)); + this._parser.registerCsiHandler('S', this._inputHandler.scrollUp.bind(this._inputHandler)); + this._parser.registerCsiHandler('T', (params, collect) => { if (params.length < 2 && !collect) { return this._inputHandler.scrollDown(params); } }); - this._parser.registerCsiHandler('X'.charCodeAt(0), this._inputHandler.eraseChars.bind(this._inputHandler)); - this._parser.registerCsiHandler('Z'.charCodeAt(0), this._inputHandler.cursorBackwardTab.bind(this._inputHandler)); - this._parser.registerCsiHandler('`'.charCodeAt(0), this._inputHandler.charPosAbsolute.bind(this._inputHandler)); - this._parser.registerCsiHandler('a'.charCodeAt(0), this._inputHandler.HPositionRelative.bind(this._inputHandler)); - this._parser.registerCsiHandler('b'.charCodeAt(0), this._inputHandler.repeatPrecedingCharacter.bind(this._inputHandler)); - this._parser.registerCsiHandler('c'.charCodeAt(0), this._inputHandler.sendDeviceAttributes.bind(this._inputHandler)); // fix collect - this._parser.registerCsiHandler('d'.charCodeAt(0), this._inputHandler.linePosAbsolute.bind(this._inputHandler)); - this._parser.registerCsiHandler('e'.charCodeAt(0), this._inputHandler.VPositionRelative.bind(this._inputHandler)); - this._parser.registerCsiHandler('f'.charCodeAt(0), this._inputHandler.HVPosition.bind(this._inputHandler)); - this._parser.registerCsiHandler('g'.charCodeAt(0), this._inputHandler.tabClear.bind(this._inputHandler)); - this._parser.registerCsiHandler('h'.charCodeAt(0), this._inputHandler.setMode.bind(this._inputHandler)); // fix collect - this._parser.registerCsiHandler('l'.charCodeAt(0), this._inputHandler.resetMode.bind(this._inputHandler)); // fix collect - this._parser.registerCsiHandler('m'.charCodeAt(0), this._inputHandler.charAttributes.bind(this._inputHandler)); - this._parser.registerCsiHandler('n'.charCodeAt(0), this._inputHandler.deviceStatus.bind(this._inputHandler)); // fix collect - this._parser.registerCsiHandler('p'.charCodeAt(0), + this._parser.registerCsiHandler('X', this._inputHandler.eraseChars.bind(this._inputHandler)); + this._parser.registerCsiHandler('Z', this._inputHandler.cursorBackwardTab.bind(this._inputHandler)); + this._parser.registerCsiHandler('`', this._inputHandler.charPosAbsolute.bind(this._inputHandler)); + this._parser.registerCsiHandler('a', this._inputHandler.HPositionRelative.bind(this._inputHandler)); + this._parser.registerCsiHandler('b', this._inputHandler.repeatPrecedingCharacter.bind(this._inputHandler)); + this._parser.registerCsiHandler('c', this._inputHandler.sendDeviceAttributes.bind(this._inputHandler)); // fix collect + this._parser.registerCsiHandler('d', this._inputHandler.linePosAbsolute.bind(this._inputHandler)); + this._parser.registerCsiHandler('e', this._inputHandler.VPositionRelative.bind(this._inputHandler)); + this._parser.registerCsiHandler('f', this._inputHandler.HVPosition.bind(this._inputHandler)); + this._parser.registerCsiHandler('g', this._inputHandler.tabClear.bind(this._inputHandler)); + this._parser.registerCsiHandler('h', this._inputHandler.setMode.bind(this._inputHandler)); // fix collect + this._parser.registerCsiHandler('l', this._inputHandler.resetMode.bind(this._inputHandler)); // fix collect + this._parser.registerCsiHandler('m', this._inputHandler.charAttributes.bind(this._inputHandler)); + this._parser.registerCsiHandler('n', this._inputHandler.deviceStatus.bind(this._inputHandler)); // fix collect + this._parser.registerCsiHandler('p', (params, collect) => { if (collect === '!') { return this._inputHandler.softReset(params); } }); - this._parser.registerCsiHandler('q'.charCodeAt(0), + this._parser.registerCsiHandler('q', (params, collect) => { if (collect === ' ') { return this._inputHandler.setCursorStyle(params); } }); - this._parser.registerCsiHandler('r'.charCodeAt(0), this._inputHandler.setScrollRegion.bind(this._inputHandler)); // fix collect - this._parser.registerCsiHandler('s'.charCodeAt(0), this._inputHandler.saveCursor.bind(this._inputHandler)); - this._parser.registerCsiHandler('u'.charCodeAt(0), this._inputHandler.restoreCursor.bind(this._inputHandler)); + this._parser.registerCsiHandler('r', this._inputHandler.setScrollRegion.bind(this._inputHandler)); // fix collect + this._parser.registerCsiHandler('s', this._inputHandler.saveCursor.bind(this._inputHandler)); + this._parser.registerCsiHandler('u', this._inputHandler.restoreCursor.bind(this._inputHandler)); + + // execute handler + this._parser.registerExecuteHandler(C0.BEL, this._inputHandler.bell.bind(this._inputHandler)); + this._parser.registerExecuteHandler(C0.LF, this._inputHandler.lineFeed.bind(this._inputHandler)); + this._parser.registerExecuteHandler(C0.VT, this._inputHandler.lineFeed.bind(this._inputHandler)); + this._parser.registerExecuteHandler(C0.FF, this._inputHandler.lineFeed.bind(this._inputHandler)); + this._parser.registerExecuteHandler(C0.CR, this._inputHandler.carriageReturn.bind(this._inputHandler)); + this._parser.registerExecuteHandler(C0.BS, this._inputHandler.backspace.bind(this._inputHandler)); + this._parser.registerExecuteHandler(C0.HT, this._inputHandler.tab.bind(this._inputHandler)); + this._parser.registerExecuteHandler(C0.SO, this._inputHandler.shiftOut.bind(this._inputHandler)); + this._parser.registerExecuteHandler(C0.SI, this._inputHandler.shiftIn.bind(this._inputHandler)); + // FIXME: What do to with missing? Old code just added those to print, but that's wrong + // behavior for most control codes. + + // OSC handler + // 0 - icon name + title + this._parser.registerOscHandler(0, this._terminal.handleTitle.bind(this._terminal)); + // 1 - icon name + // 2 - title + this._parser.registerOscHandler(2, this._terminal.handleTitle.bind(this._terminal)); + // 3 - set property X in the form "prop=value" + // 4 - Change Color Number + // 5 - Change Special Color Number + // 6 - Enable/disable Special Color Number c + // 7 - current directory? (not in xterm spec, see https://gitlab.com/gnachman/iterm2/issues/3939) + // 10 - Change VT100 text foreground color to Pt. + // 11 - Change VT100 text background color to Pt. + // 12 - Change text cursor color to Pt. + // 13 - Change mouse foreground color to Pt. + // 14 - Change mouse background color to Pt. + // 15 - Change Tektronix foreground color to Pt. + // 16 - Change Tektronix background color to Pt. + // 17 - Change highlight background color to Pt. + // 18 - Change Tektronix cursor color to Pt. + // 19 - Change highlight foreground color to Pt. + // 46 - Change Log File to Pt. + // 50 - Set Font to Pt. + // 51 - reserved for Emacs shell. + // 52 - Manipulate Selection Data. + // 104 ; c - Reset Color Number c. + // 105 ; c - Reset Special Color Number c. + // 106 ; c; f - Enable/disable Special Color Number c. + // 110 - Reset VT100 text foreground color. + // 111 - Reset VT100 text background color. + // 112 - Reset text cursor color. + // 113 - Reset mouse foreground color. + // 114 - Reset mouse background color. + // 115 - Reset Tektronix foreground color. + // 116 - Reset Tektronix background color. + // 117 - Reset highlight color. + // 118 - Reset Tektronix cursor color. + // 119 - Reset highlight foreground color. + + // ESC handlers + this._parser.registerEscHandler('', '7', this._inputHandler.saveCursor.bind(this._inputHandler)); + this._parser.registerEscHandler('', '8', this._inputHandler.restoreCursor.bind(this._inputHandler)); + this._parser.registerEscHandler('', 'D', this._terminal.index.bind(this._terminal)); + this._parser.registerEscHandler('', 'E', () => { + this._terminal.buffer.x = 0; + this._terminal.index(); + }); + this._parser.registerEscHandler('', 'H', (this._terminal).tabSet.bind(this._terminal)); + this._parser.registerEscHandler('', 'M', this._terminal.reverseIndex.bind(this._terminal)); + this._parser.registerEscHandler('', '=', () => { + this._terminal.log('Serial port requested application keypad.'); + this._terminal.applicationKeypad = true; + if (this._terminal.viewport) { + this._terminal.viewport.syncScrollArea(); + } + }); + this._parser.registerEscHandler('', '>', () => { + this._terminal.log('Switching back to normal keypad.'); + this._terminal.applicationKeypad = false; + if (this._terminal.viewport) { + this._terminal.viewport.syncScrollArea(); + } + }); + this._parser.registerEscHandler('', 'c', this._terminal.reset.bind(this._terminal)); + this._parser.registerEscHandler('', 'n', () => this._terminal.setgLevel(2)); + this._parser.registerEscHandler('', 'o', () => this._terminal.setgLevel(3)); + this._parser.registerEscHandler('', '|', () => this._terminal.setgLevel(3)); + this._parser.registerEscHandler('', '}', () => this._terminal.setgLevel(2)); + this._parser.registerEscHandler('', '~', () => this._terminal.setgLevel(1)); + + this._parser.registerEscHandler('%', '@', () => { + this._terminal.setgLevel(0); + this._terminal.setgCharset(0, DEFAULT_CHARSET); // US (default) + }); + this._parser.registerEscHandler('%', 'G', () => { + this._terminal.setgLevel(0); + this._terminal.setgCharset(0, DEFAULT_CHARSET); // US (default) + }); + for (let flag in CHARSETS) { + this._parser.registerEscHandler('(', flag, () => this._terminal.setgCharset(0, CHARSETS[flag] || DEFAULT_CHARSET)); + this._parser.registerEscHandler(')', flag, () => this._terminal.setgCharset(1, CHARSETS[flag] || DEFAULT_CHARSET)); + this._parser.registerEscHandler('*', flag, () => this._terminal.setgCharset(2, CHARSETS[flag] || DEFAULT_CHARSET)); + this._parser.registerEscHandler('+', flag, () => this._terminal.setgCharset(3, CHARSETS[flag] || DEFAULT_CHARSET)); + this._parser.registerEscHandler('-', flag, () => this._terminal.setgCharset(1, CHARSETS[flag] || DEFAULT_CHARSET)); + this._parser.registerEscHandler('.', flag, () => this._terminal.setgCharset(2, CHARSETS[flag] || DEFAULT_CHARSET)); + } } + + parse(data: string): void { const cursorStartX = this._terminal.buffer.x; const cursorStartY = this._terminal.buffer.y; @@ -590,7 +757,7 @@ export class ParserTerminal implements IParserTerminal { } } - actionPrint(data: string, start: number, end: number): void { + print(data: string, start: number, end: number): void { let ch; let code; let low; @@ -703,291 +870,4 @@ export class ParserTerminal implements IParserTerminal { } } } - - actionOSC(data: string): void { - let idx = data.indexOf(';'); - let identifier = parseInt(data.substring(0, idx)); - let content = data.substring(idx + 1); - - // TODO: call custom OSC handler here - - switch (identifier) { - case 0: - case 1: - case 2: - if (content) { - this._terminal.title = content; - this._terminal.handleTitle(this._terminal.title); - } - break; - case 3: - // set X property - break; - case 4: - case 5: - // change dynamic colors - break; - case 10: - case 11: - case 12: - case 13: - case 14: - case 15: - case 16: - case 17: - case 18: - case 19: - // change dynamic ui colors - break; - case 46: - // change log file - break; - case 50: - // dynamic font - break; - case 51: - // emacs shell - break; - case 52: - // manipulate selection data - break; - case 104: - case 105: - case 110: - case 111: - case 112: - case 113: - case 114: - case 115: - case 116: - case 117: - case 118: - // reset colors - break; - } - } - - actionExecute(flag: string): void { - // Q: No XON/XOFF handling here - where is it done? - // Q: do we need the default fallback to addChar? - switch (flag) { - case C0.BEL: return this._inputHandler.bell(); - case C0.LF: - case C0.VT: - case C0.FF: return this._inputHandler.lineFeed(); - case C0.CR: return this._inputHandler.carriageReturn(); - case C0.BS: return this._inputHandler.backspace(); - case C0.HT: return this._inputHandler.tab(); - case C0.SO: return this._inputHandler.shiftOut(); - case C0.SI: return this._inputHandler.shiftIn(); - default: - this._inputHandler.addChar(flag, flag.charCodeAt(0)); // TODO: get rid this here - } - this._terminal.error('Unknown EXEC flag: %s.', flag); - } - - actionCSI(collected: string, params: number[], flag: string): void { - this._terminal.prefix = collected; - switch (flag) { - // case '@': return this._inputHandler.insertChars(params); - // case 'A': return this._inputHandler.cursorUp(params); - // case 'B': return this._inputHandler.cursorDown(params); - // case 'C': return this._inputHandler.cursorForward(params); - // case 'D': return this._inputHandler.cursorBackward(params); - // case 'E': return this._inputHandler.cursorNextLine(params); - // case 'F': return this._inputHandler.cursorPrecedingLine(params); - // case 'G': return this._inputHandler.cursorCharAbsolute(params); - // case 'H': return this._inputHandler.cursorPosition(params); - // case 'I': return this._inputHandler.cursorForwardTab(params); - // case 'J': return this._inputHandler.eraseInDisplay(params); - // case 'K': return this._inputHandler.eraseInLine(params); - // case 'L': return this._inputHandler.insertLines(params); - // case 'M': return this._inputHandler.deleteLines(params); - // case 'P': return this._inputHandler.deleteChars(params); - // case 'S': return this._inputHandler.scrollUp(params); - // case 'T': - // // Q: Why this condition? - // if (params.length < 2 && !collected) { - // return this._inputHandler.scrollDown(params); - // } - // break; - // case 'X': return this._inputHandler.eraseChars(params); - // case 'Z': return this._inputHandler.cursorBackwardTab(params); - // case '`': return this._inputHandler.charPosAbsolute(params); - // case 'a': return this._inputHandler.HPositionRelative(params); - // case 'b': return this._inputHandler.repeatPrecedingCharacter(params); - // case 'c': return this._inputHandler.sendDeviceAttributes(params); - // case 'd': return this._inputHandler.linePosAbsolute(params); - // case 'e': return this._inputHandler.VPositionRelative(params); - // case 'f': return this._inputHandler.HVPosition(params); - // case 'g': return this._inputHandler.tabClear(params); - // case 'h': return this._inputHandler.setMode(params); - // case 'l': return this._inputHandler.resetMode(params); - // case 'm': return this._inputHandler.charAttributes(params); - // case 'n': return this._inputHandler.deviceStatus(params); - // case 'p': - // if (collected === '!') { - // return this._inputHandler.softReset(params); - // } - // break; - // case 'q': - // if (collected === ' ') { - // return this._inputHandler.setCursorStyle(params); - // } - // break; - // case 'r': return this._inputHandler.setScrollRegion(params); - // case 's': return this._inputHandler.saveCursor(params); - // case 'u': return this._inputHandler.restoreCursor(params); - } - this._terminal.error('Unknown CSI code: %s %s %s.', collected, params, flag); - } - - actionESC(collected: string, flag: string): void { - switch (collected) { - case '': - switch (flag) { - // case '6': // Back Index (DECBI), VT420 and up - not supported - case '7': // Save Cursor (DECSC) - return this._inputHandler.saveCursor(); - case '8': // Restore Cursor (DECRC) - return this._inputHandler.restoreCursor(); - // case '9': // Forward Index (DECFI), VT420 and up - not supported - case 'D': // Index (IND is 0x84) - return this._terminal.index(); - case 'E': // Next Line (NEL is 0x85) - this._terminal.buffer.x = 0; - this._terminal.index(); - return; - case 'H': // ESC H Tab Set (HTS is 0x88) - return (this._terminal).tabSet(); - case 'M': // Reverse Index (RI is 0x8d) - return this._terminal.reverseIndex(); - case 'N': // Single Shift Select of G2 Character Set ( SS2 is 0x8e) - Is this supported? - case 'O': // Single Shift Select of G3 Character Set ( SS3 is 0x8f) - return; - // case 'P': // Device Control String (DCS is 0x90) - covered by parser - // case 'V': // Start of Guarded Area (SPA is 0x96) - not supported - // case 'W': // End of Guarded Area (EPA is 0x97) - not supported - // case 'X': // Start of String (SOS is 0x98) - covered by parser (unsupported) - // case 'Z': // Return Terminal ID (DECID is 0x9a). Obsolete form of CSI c (DA). - not supported - // case '[': // Control Sequence Introducer (CSI is 0x9b) - covered by parser - // case '\': // String Terminator (ST is 0x9c) - covered by parser - // case ']': // Operating System Command (OSC is 0x9d) - covered by parser - // case '^': // Privacy Message (PM is 0x9e) - covered by parser (unsupported) - // case '_': // Application Program Command (APC is 0x9f) - covered by parser (unsupported) - case '=': // Application Keypad (DECKPAM) - this._terminal.log('Serial port requested application keypad.'); - this._terminal.applicationKeypad = true; - if (this._terminal.viewport) { - this._terminal.viewport.syncScrollArea(); - } - return; - case '>': // Normal Keypad (DECKPNM) - this._terminal.log('Switching back to normal keypad.'); - this._terminal.applicationKeypad = false; - if (this._terminal.viewport) { - this._terminal.viewport.syncScrollArea(); - } - return; - // case 'F': // Cursor to lower left corner of screen - case 'c': // Full Reset (RIS) http://vt100.net/docs/vt220-rm/chapter4.html - this._terminal.reset(); - return; - // case 'l': // Memory Lock (per HP terminals). Locks memory above the cursor. - // case 'm': // Memory Unlock (per HP terminals). - case 'n': // Invoke the G2 Character Set as GL (LS2). - return this._terminal.setgLevel(2); - case 'o': // Invoke the G3 Character Set as GL (LS3). - return this._terminal.setgLevel(3); - case '|': // Invoke the G3 Character Set as GR (LS3R). - return this._terminal.setgLevel(3); - case '}': // Invoke the G2 Character Set as GR (LS2R). - return this._terminal.setgLevel(2); - case '~': // Invoke the G1 Character Set as GR (LS1R). - return this._terminal.setgLevel(1); - } - // case ' ': - // switch (flag) { - // case 'F': // (SP) 7-bit controls (S7C1T) - // case 'G': // (SP) 8-bit controls (S8C1T) - // case 'L': // (SP) Set ANSI conformance level 1 (dpANS X3.134.1) - // case 'M': // (SP) Set ANSI conformance level 2 (dpANS X3.134.1) - // case 'N': // (SP) Set ANSI conformance level 3 (dpANS X3.134.1) - // } - - // case '#': - // switch (flag) { - // case '3': // DEC double-height line, top half (DECDHL) - // case '4': // DEC double-height line, bottom half (DECDHL) - // case '5': // DEC single-width line (DECSWL) - // case '6': // DEC double-width line (DECDWL) - // case '8': // DEC Screen Alignment Test (DECALN) - // } - - case '%': - // switch (flag) { - // case '@': // (%) Select default character set. That is ISO 8859-1 (ISO 2022) - // case 'G': // (%) Select UTF-8 character set (ISO 2022) - // } - this._terminal.setgLevel(0); - this._terminal.setgCharset(0, DEFAULT_CHARSET); // US (default) - return; - - // load character sets - case '(': // G0 (VT100) - return this._terminal.setgCharset(0, CHARSETS[flag] || DEFAULT_CHARSET); - case ')': // G1 (VT100) - return this._terminal.setgCharset(1, CHARSETS[flag] || DEFAULT_CHARSET); - case '*': // G2 (VT220) - return this._terminal.setgCharset(2, CHARSETS[flag] || DEFAULT_CHARSET); - case '+': // G3 (VT220) - return this._terminal.setgCharset(3, CHARSETS[flag] || DEFAULT_CHARSET); - case '-': // G1 (VT300) - return this._terminal.setgCharset(1, CHARSETS[flag] || DEFAULT_CHARSET); - case '.': // G2 (VT300) - return this._terminal.setgCharset(2, CHARSETS[flag] || DEFAULT_CHARSET); - case '/': // G3 (VT300) - // not supported - how to deal with this? (Q: original code is not reachable?) - return; - default: - this._terminal.error('Unknown ESC control: %s %s.', collected, flag); - } - } - - actionDCSHook(collected: string, params: number[], flag: string): void { - // TODO + custom hook - } - - actionDCSPrint(data: string): void { - // TODO + custom hook - } - - actionDCSUnhook(): void { - // TODO + custom hook - } - - actionError(): void { - // TODO - } - - // custom handler interface - // Q: explicit like below or with an event like interface? - // tricky part: DCS handler need to be stateful over several - // actionDCSPrint invocations - own base interface/abstract class type? - - registerOSCHandler(): void { - // TODO - } - - unregisterOSCHandler(): void { - // TODO - } - - registerDCSHandler(): void { - // TODO - } - - unregisterDCSHandler(): void { - // TODO - } }