From fb7e5d590c21467f777897e20dfcc3f92ec74a44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sat, 21 Apr 2018 05:12:24 +0200 Subject: [PATCH 01/44] new parser --- src/EscapeSequenceParser.test.ts | 1042 ++++++++++++++++++++++++++++++ src/EscapeSequenceParser.ts | 718 ++++++++++++++++++++ src/InputHandler.ts | 61 +- src/Terminal.ts | 15 +- 4 files changed, 1802 insertions(+), 34 deletions(-) create mode 100644 src/EscapeSequenceParser.test.ts create mode 100644 src/EscapeSequenceParser.ts diff --git a/src/EscapeSequenceParser.test.ts b/src/EscapeSequenceParser.test.ts new file mode 100644 index 0000000000..eefa760356 --- /dev/null +++ b/src/EscapeSequenceParser.test.ts @@ -0,0 +1,1042 @@ +import { AnsiParser, IParserTerminal } 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 + }, + inst_p: function (s: string, start: number, end: number): void { + this.calls.push(['print', s.substring(start, end)]); + }, + inst_o: function (s: string): void { + this.calls.push(['osc', s]); + }, + inst_x: function (flag: string): void { + this.calls.push(['exe', flag]); + }, + inst_c: function (collected: string, params: number[], flag: string): void { + this.calls.push(['csi', collected, params, flag]); + }, + inst_e: function (collected: string, flag: string): void { + this.calls.push(['esc', collected, flag]); + }, + inst_H: function (collected: string, params: number[], flag: string): void { + this.calls.push(['dcs hook', collected, params, flag]); + }, + inst_P: function (dcs: string): void { + this.calls.push(['dcs put', dcs]); + }, + inst_U: function (): void { + this.calls.push(['dcs unhook']); + } +}; + +let parser = new AnsiParser(testTerminal); + +describe('Parser init and methods', function(): void { + it('parser init', function (): void { + let p = new AnsiParser({}); + chai.expect(p.term).a('object'); + chai.expect(p.term.inst_p).a('function'); + chai.expect(p.term.inst_o).a('function'); + chai.expect(p.term.inst_x).a('function'); + chai.expect(p.term.inst_c).a('function'); + chai.expect(p.term.inst_e).a('function'); + chai.expect(p.term.inst_H).a('function'); + chai.expect(p.term.inst_P).a('function'); + chai.expect(p.term.inst_U).a('function'); + p.parse('\x1b[31mHello World!'); + }); + it('terminal callbacks', function (): void { + chai.expect(parser.term).equal(testTerminal); + chai.expect(parser.term.inst_p).equal(testTerminal.inst_p); + chai.expect(parser.term.inst_o).equal(testTerminal.inst_o); + chai.expect(parser.term.inst_x).equal(testTerminal.inst_x); + chai.expect(parser.term.inst_c).equal(testTerminal.inst_c); + chai.expect(parser.term.inst_e).equal(testTerminal.inst_e); + chai.expect(parser.term.inst_H).equal(testTerminal.inst_H); + chai.expect(parser.term.inst_P).equal(testTerminal.inst_P); + chai.expect(parser.term.inst_U).equal(testTerminal.inst_U); + }); + 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(0); + 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 = 0; + parser.parse(exes[i]); + chai.expect(parser.currentState).equal(0); + 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 = 0; + parser.parse(printables[i]); + chai.expect(parser.currentState).equal(0); + 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 (let state = 0; state < 14; ++state) { + for (let i = 0; i < exes.length; ++i) { + parser.currentState = state; + parser.parse(exes[i]); + chai.expect(parser.currentState).equal(0); + testTerminal.compare(((exceptions[state]) ? exceptions[state][exes[i]] : 0) || [['exe', exes[i]]]); + parser.reset(); + testTerminal.clear(); + } + parser.parse('\x9c'); + chai.expect(parser.currentState).equal(0); + testTerminal.compare([]); + parser.reset(); + testTerminal.clear(); + } + }); + it('trans ANYWHERE --> ESCAPE with clear', function (): void { + parser.reset(); + for (let state = 0; state < 14; ++state) { + parser.currentState = state; + parser.osc = '#'; + parser.params = [23]; + parser.collected = '#'; + parser.parse('\x1b'); + chai.expect(parser.currentState).equal(1); + 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 = 1; + parser.parse(exes[i]); + chai.expect(parser.currentState).equal(1); + testTerminal.compare([['exe', exes[i]]]); + parser.reset(); + testTerminal.clear(); + } + }); + it('state ESCAPE ignore', function (): void { + parser.reset(); + testTerminal.clear(); + parser.currentState = 1; + parser.parse('\x7f'); + chai.expect(parser.currentState).equal(1); + 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 = 1; + parser.parse(dispatches[i]); + chai.expect(parser.currentState).equal(0); + 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 = 1; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(2); + 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 = 2; + parser.parse(exes[i]); + chai.expect(parser.currentState).equal(2); + testTerminal.compare([['exe', exes[i]]]); + parser.reset(); + testTerminal.clear(); + } + }); + it('state ESCAPE_INTERMEDIATE ignore', function (): void { + parser.reset(); + testTerminal.clear(); + parser.currentState = 2; + parser.parse('\x7f'); + chai.expect(parser.currentState).equal(2); + 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 = 2; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(2); + 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 = 2; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(0); + testTerminal.compare([['esc', '', collect[i]]]); + parser.reset(); + testTerminal.clear(); + } + }); + it('trans ANYWHERE/ESCAPE --> CSI_ENTRY with clear', function (): void { + parser.reset(); + // C0 + parser.currentState = 1; + parser.osc = '#'; + parser.params = [123]; + parser.collected = '#'; + parser.parse('['); + chai.expect(parser.currentState).equal(3); + chai.expect(parser.osc).equal(''); + chai.expect(parser.params).eql([0]); + chai.expect(parser.collected).equal(''); + parser.reset(); + // C1 + for (let state = 0; state < 14; ++state) { + parser.currentState = state; + parser.osc = '#'; + parser.params = [123]; + parser.collected = '#'; + parser.parse('\x9b'); + chai.expect(parser.currentState).equal(3); + 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 = 3; + parser.parse(exes[i]); + chai.expect(parser.currentState).equal(3); + testTerminal.compare([['exe', exes[i]]]); + parser.reset(); + testTerminal.clear(); + } + }); + it('state CSI_ENTRY ignore', function (): void { + parser.reset(); + testTerminal.clear(); + parser.currentState = 3; + parser.parse('\x7f'); + chai.expect(parser.currentState).equal(3); + 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 = 3; + parser.parse(dispatches[i]); + chai.expect(parser.currentState).equal(0); + 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 = 3; + parser.parse(params[i]); + chai.expect(parser.currentState).equal(4); + chai.expect(parser.params).eql([params[i].charCodeAt(0) - 48]); + parser.reset(); + } + // ';' + parser.currentState = 3; + parser.parse('\x3b'); + chai.expect(parser.currentState).equal(4); + chai.expect(parser.params).eql([0, 0]); + parser.reset(); + for (let i = 0; i < collect.length; ++i) { + parser.currentState = 3; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(4); + 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 = 4; + parser.parse(exes[i]); + chai.expect(parser.currentState).equal(4); + 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 = 4; + parser.parse(params[i]); + chai.expect(parser.currentState).equal(4); + chai.expect(parser.params).eql([params[i].charCodeAt(0) - 48]); + parser.reset(); + } + parser.currentState = 4; + parser.parse('\x3b'); + chai.expect(parser.currentState).equal(4); + chai.expect(parser.params).eql([0, 0]); + parser.reset(); + }); + it('state CSI_PARAM ignore', function (): void { + parser.reset(); + testTerminal.clear(); + parser.currentState = 4; + parser.parse('\x7f'); + chai.expect(parser.currentState).equal(4); + 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 = 4; + parser.params = [0, 1]; + parser.parse(dispatches[i]); + chai.expect(parser.currentState).equal(0); + 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 = 3; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(5); + 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 = 4; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(5); + 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 = 5; + parser.parse(exes[i]); + chai.expect(parser.currentState).equal(5); + 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 = 5; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(5); + chai.expect(parser.collected).equal(collect[i]); + parser.reset(); + } + }); + it('state CSI_INTERMEDIATE ignore', function (): void { + parser.reset(); + testTerminal.clear(); + parser.currentState = 5; + parser.parse('\x7f'); + chai.expect(parser.currentState).equal(5); + 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 = 5; + parser.params = [0, 1]; + parser.parse(dispatches[i]); + chai.expect(parser.currentState).equal(0); + testTerminal.compare([['csi', '', [0, 1], dispatches[i]]]); + parser.reset(); + testTerminal.clear(); + } + }); + it('trans CSI_ENTRY --> CSI_IGNORE', function (): void { + parser.reset(); + parser.currentState = 3; + parser.parse('\x3a'); + chai.expect(parser.currentState).equal(6); + 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 = 4; + parser.parse('\x3b' + chars[i]); + chai.expect(parser.currentState).equal(6); + 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 = 5; + parser.parse(chars[i]); + chai.expect(parser.currentState).equal(6); + 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 = 6; + parser.parse(exes[i]); + chai.expect(parser.currentState).equal(6); + 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 = 6; + parser.parse(ignored[i]); + chai.expect(parser.currentState).equal(6); + 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 = 6; + parser.params = [0, 1]; + parser.parse(dispatches[i]); + chai.expect(parser.currentState).equal(0); + 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(7); + parser.reset(); + } + // C1 + for (let state = 0; state < 14; ++state) { + parser.currentState = state; + initializers = ['\x98', '\x9e', '\x9f']; + for (let i = 0; i < initializers.length; ++i) { + parser.parse(initializers[i]); + chai.expect(parser.currentState).equal(7); + 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 = 7; + parser.parse(ignored[i]); + chai.expect(parser.currentState).equal(7); + parser.reset(); + } + }); + it('trans ANYWHERE/ESCAPE --> OSC_STRING', function (): void { + parser.reset(); + // C0 + parser.parse('\x1b]'); + chai.expect(parser.currentState).equal(8); + parser.reset(); + // C1 + for (let state = 0; state < 14; ++state) { + parser.currentState = state; + parser.parse('\x9d'); + chai.expect(parser.currentState).equal(8); + 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 = 8; + parser.parse(ignored[i]); + chai.expect(parser.currentState).equal(8); + 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 = 8; + parser.parse(puts[i]); + chai.expect(parser.currentState).equal(8); + 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(9); + parser.reset(); + // C1 + for (let state = 0; state < 14; ++state) { + parser.currentState = state; + parser.parse('\x90'); + chai.expect(parser.currentState).equal(9); + 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 = 9; + parser.parse(ignored[i]); + chai.expect(parser.currentState).equal(9); + 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 = 9; + parser.parse(params[i]); + chai.expect(parser.currentState).equal(10); + chai.expect(parser.params).eql([params[i].charCodeAt(0) - 48]); + parser.reset(); + } + parser.currentState = 9; + parser.parse('\x3b'); + chai.expect(parser.currentState).equal(10); + chai.expect(parser.params).eql([0, 0]); + parser.reset(); + for (let i = 0; i < collect.length; ++i) { + parser.currentState = 9; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(10); + 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 = 10; + parser.parse(ignored[i]); + chai.expect(parser.currentState).equal(10); + 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 = 10; + parser.parse(params[i]); + chai.expect(parser.currentState).equal(10); + chai.expect(parser.params).eql([params[i].charCodeAt(0) - 48]); + parser.reset(); + } + parser.currentState = 10; + parser.parse('\x3b'); + chai.expect(parser.currentState).equal(10); + chai.expect(parser.params).eql([0, 0]); + parser.reset(); + }); + it('trans DCS_ENTRY --> DCS_IGNORE', function (): void { + parser.reset(); + parser.currentState = 9; + parser.parse('\x3a'); + chai.expect(parser.currentState).equal(11); + 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 = 10; + parser.parse('\x3b' + chars[i]); + chai.expect(parser.currentState).equal(11); + 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 = 12; + parser.parse(chars[i]); + chai.expect(parser.currentState).equal(11); + 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 = 11; + parser.parse(ignored[i]); + chai.expect(parser.currentState).equal(11); + 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 = 9; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(12); + 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 = 10; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(12); + 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 = 12; + parser.parse(ignored[i]); + chai.expect(parser.currentState).equal(12); + 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 = 12; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(12); + 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 = 12; + parser.parse('\x20' + chars[i]); + chai.expect(parser.currentState).equal(11); + 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 = 9; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(13); + 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 = 10; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(13); + 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 = 12; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(13); + 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 = 13; + parser.parse(puts[i]); + chai.expect(parser.currentState).equal(13); + testTerminal.compare([['dcs put', puts[i]]]); + parser.reset(); + testTerminal.clear(); + } + }); + it('state DCS_PASSTHROUGH ignore', function (): void { + parser.reset(); + testTerminal.clear(); + parser.currentState = 13; + parser.parse('\x7f'); + chai.expect(parser.currentState).equal(13); + 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 = 6; + parser.parse('€öäü'); + chai.expect(parser.currentState).equal(6); + testTerminal.compare([]); + parser.reset(); + testTerminal.clear(); + }); + it('DCS_IGNORE error', function (): void { + parser.reset(); + testTerminal.clear(); + parser.currentState = 11; + parser.parse('€öäü'); + chai.expect(parser.currentState).equal(11); + testTerminal.compare([]); + parser.reset(); + testTerminal.clear(); + }); + it('DCS_PASSTHROUGH error', function (): void { + parser.reset(); + testTerminal.clear(); + parser.currentState = 13; + parser.parse('€öäü'); + chai.expect(parser.currentState).equal(13); + testTerminal.compare([['dcs put', '€öäü']]); + parser.reset(); + testTerminal.clear(); + }); + it('error else of if (code > 159)', function (): void { + parser.reset(); + testTerminal.clear(); + parser.currentState = 0; + parser.parse('\x1e'); + chai.expect(parser.currentState).equal(0); + testTerminal.compare([]); + parser.reset(); + testTerminal.clear(); + }); +}); + +let errorTerminal1 = function(): void {}; +errorTerminal1.prototype = testTerminal; +let errTerminal1 = new errorTerminal1(); +errTerminal1.inst_E = function(e: any): void { + this.calls.push(['error', e]); +}; +let errParser1 = new AnsiParser(errTerminal1); + +let errorTerminal2 = function(): void {}; +errorTerminal2.prototype = testTerminal; +let errTerminal2 = new errorTerminal2(); +errTerminal2.inst_E = function(e: any): any { + this.calls.push(['error', e]); + return true; // --> abort parsing +}; +let errParser2 = new AnsiParser(errTerminal2); + +describe('error tests', function(): void { + it('CSI_PARAM unicode error - inst_E output w/o abort', function (): void { + errParser1.parse('\x1b[<31;5€normal print'); + errTerminal1.compare([ + ['error', { + pos: 7, + character: '€', + state: 4, + print: -1, + dcs: -1, + osc: '', + collect: '<', + params: [31, 5]}], + ['print', 'normal print'] + ]); + parser.reset(); + testTerminal.clear(); + }); + it('CSI_PARAM unicode error - inst_E output with abort', function (): void { + errParser2.parse('\x1b[<31;5€no print'); + errTerminal2.compare([ + ['error', { + pos: 7, + character: '€', + state: 4, + print: -1, + dcs: -1, + osc: '', + collect: '<', + params: [31, 5]}] + ]); + parser.reset(); + testTerminal.clear(); + }); +}); diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts new file mode 100644 index 0000000000..6facddeab8 --- /dev/null +++ b/src/EscapeSequenceParser.ts @@ -0,0 +1,718 @@ +export interface IParserTerminal { + inst_p?: (s: string, start: number, end: number) => void; + inst_o?: (s: string) => void; + inst_x?: (flag: string) => void; + inst_c?: (collected: string, params: number[], flag: string) => void; + inst_e?: (collected: string, flag: string) => void; + inst_H?: (collected: string, params: number[], flag: string) => void; + inst_P?: (dcs: string) => void; + inst_U?: () => void; + inst_E?: () => void; // TODO: real signature +} + + +export function r(a: number, b: number): number[] { + let c = b - a; + let arr = new Array(c); + while (c--) { + arr[c] = --b; + } + return arr; +} + + +export class TransitionTable { + public table: Uint8Array; + constructor(length: number) { + this.table = new Uint8Array(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_list(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); + } + } +} + + +let PRINTABLES = r(0x20, 0x7f); +let EXECUTABLES = r(0x00, 0x18); +EXECUTABLES.push(0x19); +EXECUTABLES.concat(r(0x1c, 0x20)); + + +export const TRANSITION_TABLE = (function (): TransitionTable { + let t: TransitionTable = new TransitionTable(4095); + + // table with default transition [any] --> [error, GROUND] + for (let state = 0; state < 14; ++state) { + for (let code = 0; code < 160; ++code) { + t[state << 8 | code] = 16; + } + } + + // apply transitions + // printables + t.add_list(PRINTABLES, 0, 2, 0); + // global anywhere rules + for (let state = 0; state < 14; ++state) { + t.add_list([0x18, 0x1a, 0x99, 0x9a], state, 3, 0); + t.add_list(r(0x80, 0x90), state, 3, 0); + t.add_list(r(0x90, 0x98), state, 3, 0); + t.add(0x9c, state, 0, 0); // ST as terminator + t.add(0x1b, state, 11, 1); // ESC + t.add(0x9d, state, 4, 8); // OSC + t.add_list([0x98, 0x9e, 0x9f], state, 0, 7); + t.add(0x9b, state, 11, 3); // CSI + t.add(0x90, state, 11, 9); // DCS + } + // rules for executables and 7f + t.add_list(EXECUTABLES, 0, 3, 0); + t.add_list(EXECUTABLES, 1, 3, 1); + t.add(0x7f, 1, null, 1); + t.add_list(EXECUTABLES, 8, null, 8); + t.add_list(EXECUTABLES, 3, 3, 3); + t.add(0x7f, 3, null, 3); + t.add_list(EXECUTABLES, 4, 3, 4); + t.add(0x7f, 4, null, 4); + t.add_list(EXECUTABLES, 6, 3, 6); + t.add_list(EXECUTABLES, 5, 3, 5); + t.add(0x7f, 5, null, 5); + t.add_list(EXECUTABLES, 2, 3, 2); + t.add(0x7f, 2, null, 2); + // osc + t.add(0x5d, 1, 4, 8); + t.add_list(PRINTABLES, 8, 5, 8); + t.add(0x7f, 8, 5, 8); + t.add_list([0x9c, 0x1b, 0x18, 0x1a, 0x07], 8, 6, 0); + t.add_list(r(0x1c, 0x20), 8, 0, 8); + // sos/pm/apc does nothing + t.add_list([0x58, 0x5e, 0x5f], 1, 0, 7); + t.add_list(PRINTABLES, 7, null, 7); + t.add_list(EXECUTABLES, 7, null, 7); + t.add(0x9c, 7, 0, 0); + // csi entries + t.add(0x5b, 1, 11, 3); + t.add_list(r(0x40, 0x7f), 3, 7, 0); + t.add_list(r(0x30, 0x3a), 3, 8, 4); + t.add(0x3b, 3, 8, 4); + t.add_list([0x3c, 0x3d, 0x3e, 0x3f], 3, 9, 4); + t.add_list(r(0x30, 0x3a), 4, 8, 4); + t.add(0x3b, 4, 8, 4); + t.add_list(r(0x40, 0x7f), 4, 7, 0); + t.add_list([0x3a, 0x3c, 0x3d, 0x3e, 0x3f], 4, 0, 6); + t.add_list(r(0x20, 0x40), 6, null, 6); + t.add(0x7f, 6, null, 6); + t.add_list(r(0x40, 0x7f), 6, 0, 0); + t.add(0x3a, 3, 0, 6); + t.add_list(r(0x20, 0x30), 3, 9, 5); + t.add_list(r(0x20, 0x30), 5, 9, 5); + t.add_list(r(0x30, 0x40), 5, 0, 6); + t.add_list(r(0x40, 0x7f), 5, 7, 0); + t.add_list(r(0x20, 0x30), 4, 9, 5); + // esc_intermediate + t.add_list(r(0x20, 0x30), 1, 9, 2); + t.add_list(r(0x20, 0x30), 2, 9, 2); + t.add_list(r(0x30, 0x7f), 2, 10, 0); + t.add_list(r(0x30, 0x50), 1, 10, 0); + t.add_list([0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x59, 0x5a, 0x5c], 1, 10, 0); + t.add_list(r(0x60, 0x7f), 1, 10, 0); + // dcs entry + t.add(0x50, 1, 11, 9); + t.add_list(EXECUTABLES, 9, null, 9); + t.add(0x7f, 9, null, 9); + t.add_list(r(0x1c, 0x20), 9, null, 9); + t.add_list(r(0x20, 0x30), 9, 9, 12); + t.add(0x3a, 9, 0, 11); + t.add_list(r(0x30, 0x3a), 9, 8, 10); + t.add(0x3b, 9, 8, 10); + t.add_list([0x3c, 0x3d, 0x3e, 0x3f], 9, 9, 10); + t.add_list(EXECUTABLES, 11, null, 11); + t.add_list(r(0x20, 0x80), 11, null, 11); + t.add_list(r(0x1c, 0x20), 11, null, 11); + t.add_list(EXECUTABLES, 10, null, 10); + t.add(0x7f, 10, null, 10); + t.add_list(r(0x1c, 0x20), 10, null, 10); + t.add_list(r(0x30, 0x3a), 10, 8, 10); + t.add(0x3b, 10, 8, 10); + t.add_list([0x3a, 0x3c, 0x3d, 0x3e, 0x3f], 10, 0, 11); + t.add_list(r(0x20, 0x30), 10, 9, 12); + t.add_list(EXECUTABLES, 12, null, 12); + t.add(0x7f, 12, null, 12); + t.add_list(r(0x1c, 0x20), 12, null, 12); + t.add_list(r(0x20, 0x30), 12, 9, 12); + t.add_list(r(0x30, 0x40), 12, 0, 11); + t.add_list(r(0x40, 0x7f), 12, 12, 13); + t.add_list(r(0x40, 0x7f), 10, 12, 13); + t.add_list(r(0x40, 0x7f), 9, 12, 13); + t.add_list(EXECUTABLES, 13, 13, 13); + t.add_list(PRINTABLES, 13, 13, 13); + t.add(0x7f, 13, null, 13); + t.add_list([0x1b, 0x9c], 13, 14, 0); + + return t; +})(); + +export class AnsiParser { + public initialState: number; + public currentState: number; + public transitions: TransitionTable; + public osc: string; + public params: number[]; + public collected: string; + public term: any; + constructor(terminal: IParserTerminal) { + this.initialState = 0; + this.currentState = this.initialState | 0; + this.transitions = new TransitionTable(4095); + this.transitions.table.set(TRANSITION_TABLE.table); + this.osc = ''; + this.params = [0]; + this.collected = ''; + this.term = terminal || {}; + let instructions = ['inst_p', 'inst_o', 'inst_x', 'inst_c', + 'inst_e', 'inst_H', 'inst_P', 'inst_U', 'inst_E']; + for (let i = 0; i < instructions.length; ++i) { + if (!(instructions[i] in this.term)) { + this.term[instructions[i]] = function(): void {}; + } + } + } + reset(): void { + this.currentState = this.initialState; + this.osc = ''; + this.params = [0]; + this.collected = ''; + } + parse(s: string): void { + let code = 0; + let transition = 0; + let error = false; + let currentState = this.currentState; + + // local buffers + let printed = -1; + let dcs = -1; + let osc = this.osc; + let collected = this.collected; + let params = this.params; + let table: Uint8Array = this.transitions.table; + + // process input string + let l = s.length; + for (let i = 0; i < l; ++i) { + code = s.charCodeAt(i); + // shortcut for most chars (print action) + if (currentState === 0 && (code > 0x1f && code < 0x80)) { + printed = (~printed) ? printed : i; + continue; + } + if (currentState === 4) { + if (code === 0x3b) { + params.push(0); + continue; + } + if (code > 0x2f && code < 0x39) { + params[params.length - 1] = params[params.length - 1] * 10 + code - 48; + continue; + } + } + transition = ((code < 0xa0) ? (table[currentState << 8 | code]) : 16); + switch (transition >> 4) { + case 2: // print + printed = (~printed) ? printed : i; + break; + case 3: // execute + if (printed + 1) { + this.term.inst_p(s, printed, i); + printed = -1; + } + this.term.inst_x(String.fromCharCode(code)); + break; + case 0: // ignore + // handle leftover print and dcs chars + if (printed + 1) { + this.term.inst_p(s, printed, i); + printed = -1; + } else if (dcs + 1) { + this.term.inst_P(s.substring(dcs, i)); + dcs = -1; + } + break; + case 1: // error + // handle unicode chars in write buffers w'o state change + if (code > 0x9f) { + switch (currentState) { + case 0: // GROUND -> add char to print string + printed = (~printed) ? printed : i; + break; + case 8: // OSC_STRING -> add char to osc string + osc += String.fromCharCode(code); + transition |= 8; + break; + case 6: // CSI_IGNORE -> ignore char + transition |= 6; + break; + case 11: // DCS_IGNORE -> ignore char + transition |= 11; + break; + case 13: // DCS_PASSTHROUGH -> add char to dcs + if (!(~dcs)) dcs = i | 0; + transition |= 13; + break; + default: // real error + error = true; + } + } else { // real error + error = true; + } + if (error) { + if (this.term.inst_E( + { + pos: i, // position in parse string + character: String.fromCharCode(code), // wrong character + state: currentState, // in state + print: printed, // print buffer + dcs: dcs, // dcs buffer + osc: osc, // osc buffer + collect: collected, // collect buffer + params: params // params buffer + })) { + return; + } + error = false; + } + break; + case 7: // csi_dispatch + this.term.inst_c(collected, params, String.fromCharCode(code)); + break; + case 8: // param + if (code === 0x3b) params.push(0); + else params[params.length - 1] = params[params.length - 1] * 10 + code - 48; + break; + case 9: // collect + collected += String.fromCharCode(code); + break; + case 10: // esc_dispatch + this.term.inst_e(collected, String.fromCharCode(code)); + break; + case 11: // clear + if (~printed) { + this.term.inst_p(s, printed, i); + printed = -1; + } + osc = ''; + params = [0]; + collected = ''; + dcs = -1; + break; + case 12: // dcs_hook + this.term.inst_H(collected, params, String.fromCharCode(code)); + break; + case 13: // dcs_put + if (!(~dcs)) dcs = i; + break; + case 14: // dcs_unhook + if (~dcs) this.term.inst_P(s.substring(dcs, i)); + this.term.inst_U(); + if (code === 0x1b) transition |= 1; + osc = ''; + params = [0]; + collected = ''; + dcs = -1; + break; + case 4: // osc_start + if (~printed) { + this.term.inst_p(s, printed, i); + printed = -1; + } + osc = ''; + break; + case 5: // osc_put + osc += s.charAt(i); + break; + case 6: // osc_end + if (osc && code !== 0x18 && code !== 0x1a) this.term.inst_o(osc); + if (code === 0x1b) transition |= 1; + osc = ''; + params = [0]; + collected = ''; + dcs = -1; + break; + } + currentState = transition & 15; + } + + // push leftover pushable buffers to terminal + if (!currentState && (printed + 1)) { + this.term.inst_p(s, printed, s.length); + } else if (currentState === 13 && (dcs + 1)) { + this.term.inst_P(s.substring(dcs)); + } + + // save non pushable buffers + this.osc = osc; + this.collected = collected; + this.params = params; + + // save state + this.currentState = currentState; + } +} + + + + + +import { IInputHandler, IInputHandlingTerminal } from './Types'; +import { CHARSETS, DEFAULT_CHARSET } from './Charsets'; +import { C0 } from './EscapeSequences'; + +// glue code between AnsiParser and Terminal +export class ParserTerminal implements IParserTerminal { + private _parser: AnsiParser; + private _terminal: any; + private _inputHandler: IInputHandler; + + constructor(_terminal: any, _inputHandler: IInputHandler) { + this._parser = new AnsiParser(this); + this._terminal = _terminal; + this._inputHandler = _inputHandler; + } + + write(data: string): void { + const cursorStartX = this._terminal.buffer.x; + const cursorStartY = this._terminal.buffer.y; + if (this._terminal.debug) { + this._terminal.log('data: ' + data); + } + // apply leftover surrogate high from last write + if (this._terminal.surrogate_high) { + data = this._terminal.surrogate_high + data; + this._terminal.surrogate_high = ''; + } + + this._parser.parse(data); + + if (this._terminal.buffer.x !== cursorStartX || this._terminal.buffer.y !== cursorStartY) { + this._terminal.emit('cursormove'); + } + } + + inst_p(data: string, start: number, end: number): void { + // const l = data.length; + let ch; + let code; + let low; + for (let i = start; i < end; ++i) { + ch = data.charAt(i); + code = data.charCodeAt(i); + if (0xD800 <= code && code <= 0xDBFF) { + // we got a surrogate high + // get surrogate low (next 2 bytes) + low = data.charCodeAt(i + 1); + if (isNaN(low)) { + // end of data stream, save surrogate high + this._terminal.surrogate_high = ch; + continue; + } + code = ((code - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000; + ch += data.charAt(i + 1); + } + // surrogate low - already handled above + if (0xDC00 <= code && code <= 0xDFFF) { + continue; + } + this._inputHandler.addChar(ch, code); + } + } + + inst_o(data: string): void { + let params = data.split(';'); + switch (parseInt(params[0])) { + case 0: + case 1: + case 2: + if (params[1]) { + this._terminal.title = params[1]; + 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; + } + } + + inst_x(flag: string): void { + switch (flag) { + case C0.BEL: return this._inputHandler.bell(); + case C0.LF: return this._inputHandler.lineFeed(); + case C0.VT: return this._inputHandler.lineFeed(); + 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)); + } + this._terminal.error('Unknown EXEC flag: %s.', flag); + } + + inst_c(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': + 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); + } + + inst_e(collected: string, flag: string): void { + let cs; + + switch (collected) { + case '': + switch (flag) { + // case '6': // Back Index (DECBI), VT420 and up - not supported + case '7': // Save Cursor (DECSC) + this._inputHandler.saveCursor(); + return; + case '8': // Restore Cursor (DECRC) + this._inputHandler.restoreCursor(); + return; + // case '9': // Forward Index (DECFI), VT420 and up - not supported + case 'D': // Index (IND is 0x84) + this._terminal.index(); + return; + 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) + (this._terminal).tabSet(); + return; + case 'M': // Reverse Index (RI is 0x8d) + this._terminal.reverseIndex(); + return; + 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). + this._terminal.setgLevel(2); + return; + case 'o': // Invoke the G3 Character Set as GL (LS3). + this._terminal.setgLevel(3); + return; + case '|': // Invoke the G3 Character Set as GR (LS3R). + this._terminal.setgLevel(3); + return; + case '}': // Invoke the G2 Character Set as GR (LS2R). + this._terminal.setgLevel(2); + return; + case '~': // Invoke the G1 Character Set as GR (LS1R). + this._terminal.setgLevel(1); + return; + } + // 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) + cs = CHARSETS[flag]; + if (!cs) cs = DEFAULT_CHARSET; + this._terminal.setgCharset(0, cs); + return; + case ')': // G1 (VT100) + cs = CHARSETS[flag]; + if (!cs) cs = DEFAULT_CHARSET; + this._terminal.setgCharset(1, cs); + return; + case '*': // G2 (VT220) + cs = CHARSETS[flag]; + if (!cs) cs = DEFAULT_CHARSET; + this._terminal.setgCharset(2, cs); + return; + case '+': // G3 (VT220) + cs = CHARSETS[flag]; + if (!cs) cs = DEFAULT_CHARSET; + this._terminal.setgCharset(3, cs); + return; + case '-': // G1 (VT300) + cs = CHARSETS[flag]; + if (!cs) cs = DEFAULT_CHARSET; + this._terminal.setgCharset(1, cs); + return; + case '.': // G2 (VT300) + if (!cs) cs = DEFAULT_CHARSET; + this._terminal.setgCharset(2, cs); + return; + case '/': // G3 (VT300) + // not supported - how to deal with this? (original code is not reachable) + return; + default: + this._terminal.error('Unknown ESC control: %s %s.', collected, flag); + } + } + + inst_H(collected: string, params: number[], flag: string): void { + // TODO + } + + inst_P(dcs: string): void { + // TODO + } + + inst_U(): void { + // TODO + } + + inst_E(): void { + // TODO + } +} diff --git a/src/InputHandler.ts b/src/InputHandler.ts index acf7af1fd0..3d29f7b42d 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -22,11 +22,14 @@ export class InputHandler implements IInputHandler { constructor(private _terminal: IInputHandlingTerminal) { } public addChar(char: string, code: number): void { - if (char >= ' ') { + // if (char >= ' ') { // calculate print space // expensive call, therefore we save width in line buffer const chWidth = wcwidth(code); + // localize buffer + let buffer = this._terminal.buffer; + if (this._terminal.charset && this._terminal.charset[char]) { char = this._terminal.charset[char]; } @@ -35,42 +38,42 @@ export class InputHandler implements IInputHandler { this._terminal.emit('a11y.char', char); } - let row = this._terminal.buffer.y + this._terminal.buffer.ybase; + let row = buffer.y + buffer.ybase; // insert combining char in last cell // FIXME: needs handling after cursor jumps - if (!chWidth && this._terminal.buffer.x) { + if (!chWidth && buffer.x) { // dont overflow left - if (this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1]) { - if (!this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1][CHAR_DATA_WIDTH_INDEX]) { + if (buffer.lines.get(row)[buffer.x - 1]) { + if (!buffer.lines.get(row)[buffer.x - 1][CHAR_DATA_WIDTH_INDEX]) { // found empty cell after fullwidth, need to go 2 cells back - if (this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 2]) { - this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 2][CHAR_DATA_CHAR_INDEX] += char; - this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 2][3] = char.charCodeAt(0); + if (buffer.lines.get(row)[buffer.x - 2]) { + buffer.lines.get(row)[buffer.x - 2][CHAR_DATA_CHAR_INDEX] += char; + buffer.lines.get(row)[buffer.x - 2][3] = char.charCodeAt(0); } } else { - this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1][CHAR_DATA_CHAR_INDEX] += char; - this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1][3] = char.charCodeAt(0); + buffer.lines.get(row)[buffer.x - 1][CHAR_DATA_CHAR_INDEX] += char; + buffer.lines.get(row)[buffer.x - 1][3] = char.charCodeAt(0); } - this._terminal.updateRange(this._terminal.buffer.y); + this._terminal.updateRange(buffer.y); } return; } // goto next line if ch would overflow // TODO: needs a global min terminal width of 2 - if (this._terminal.buffer.x + chWidth - 1 >= this._terminal.cols) { + if (buffer.x + chWidth - 1 >= this._terminal.cols) { // autowrap - DECAWM if (this._terminal.wraparoundMode) { - this._terminal.buffer.x = 0; - this._terminal.buffer.y++; - if (this._terminal.buffer.y > this._terminal.buffer.scrollBottom) { - this._terminal.buffer.y--; + buffer.x = 0; + buffer.y++; + if (buffer.y > buffer.scrollBottom) { + buffer.y--; this._terminal.scroll(true); } else { // The line already exists (eg. the initial viewport), mark it as a // wrapped line - (this._terminal.buffer.lines.get(this._terminal.buffer.y)).isWrapped = true; + (buffer.lines.get(buffer.y)).isWrapped = true; } } else { if (chWidth === 2) { // FIXME: check for xterm behavior @@ -78,7 +81,7 @@ export class InputHandler implements IInputHandler { } } } - row = this._terminal.buffer.y + this._terminal.buffer.ybase; + row = buffer.y + buffer.ybase; // insert mode: move characters to right if (this._terminal.insertMode) { @@ -86,28 +89,28 @@ export class InputHandler implements IInputHandler { for (let moves = 0; moves < chWidth; ++moves) { // remove last cell, if it's width is 0 // we have to adjust the second last cell as well - const removed = this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).pop(); + const removed = buffer.lines.get(buffer.y + buffer.ybase).pop(); if (removed[CHAR_DATA_WIDTH_INDEX] === 0 - && this._terminal.buffer.lines.get(row)[this._terminal.cols - 2] - && this._terminal.buffer.lines.get(row)[this._terminal.cols - 2][CHAR_DATA_WIDTH_INDEX] === 2) { - this._terminal.buffer.lines.get(row)[this._terminal.cols - 2] = [this._terminal.curAttr, ' ', 1, ' '.charCodeAt(0)]; + && buffer.lines.get(row)[this._terminal.cols - 2] + && buffer.lines.get(row)[this._terminal.cols - 2][CHAR_DATA_WIDTH_INDEX] === 2) { + buffer.lines.get(row)[this._terminal.cols - 2] = [this._terminal.curAttr, ' ', 1, ' '.charCodeAt(0)]; } // insert empty cell at cursor - this._terminal.buffer.lines.get(row).splice(this._terminal.buffer.x, 0, [this._terminal.curAttr, ' ', 1, ' '.charCodeAt(0)]); + buffer.lines.get(row).splice(buffer.x, 0, [this._terminal.curAttr, ' ', 1, ' '.charCodeAt(0)]); } } - this._terminal.buffer.lines.get(row)[this._terminal.buffer.x] = [this._terminal.curAttr, char, chWidth, char.charCodeAt(0)]; - this._terminal.buffer.x++; - this._terminal.updateRange(this._terminal.buffer.y); + buffer.lines.get(row)[buffer.x] = [this._terminal.curAttr, char, chWidth, char.charCodeAt(0)]; + buffer.x++; + this._terminal.updateRange(buffer.y); // fullwidth char - set next cell width to zero and advance cursor if (chWidth === 2) { - this._terminal.buffer.lines.get(row)[this._terminal.buffer.x] = [this._terminal.curAttr, '', 0, undefined]; - this._terminal.buffer.x++; + buffer.lines.get(row)[buffer.x] = [this._terminal.curAttr, '', 0, undefined]; + buffer.x++; } - } + // } } /** diff --git a/src/Terminal.ts b/src/Terminal.ts index 233b8a17ff..5bec8a9a09 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -32,7 +32,7 @@ import { Viewport } from './Viewport'; import { rightClickHandler, moveTextAreaUnderMouseCursor, pasteHandler, copyHandler } from './handlers/Clipboard'; import { C0 } from './EscapeSequences'; import { InputHandler } from './InputHandler'; -import { Parser } from './Parser'; +// import { Parser } from './Parser'; import { Renderer } from './renderer/Renderer'; import { Linkifier } from './Linkifier'; import { SelectionManager } from './SelectionManager'; @@ -47,6 +47,7 @@ import { MouseZoneManager } from './input/MouseZoneManager'; import { AccessibilityManager } from './AccessibilityManager'; import { ScreenDprMonitor } from './utils/ScreenDprMonitor'; import { ITheme, ILocalizableStrings, IMarker } from 'xterm'; +import { ParserTerminal } from './EscapeSequenceParser'; // reg + shift key mappings for digits and special chars const KEYCODE_KEY_MAPPINGS = { @@ -212,7 +213,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT private _inputHandler: InputHandler; public soundManager: SoundManager; - private _parser: Parser; + // private _parser: Parser; + private _newParser: ParserTerminal; public renderer: IRenderer; public selectionManager: SelectionManager; public linkifier: ILinkifier; @@ -306,7 +308,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this._userScrolling = false; this._inputHandler = new InputHandler(this); - this._parser = new Parser(this._inputHandler, this); + // this._parser = new Parser(this._inputHandler, this); + this._newParser = new ParserTerminal(this, this._inputHandler); // Reuse renderer if the Terminal is being recreated via a reset call. this.renderer = this.renderer || null; this.selectionManager = this.selectionManager || null; @@ -1303,8 +1306,10 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT // middle of parsing escape sequence in two chunks. For some reason the // state of the parser resets to 0 after exiting parser.parse. This change // just sets the state back based on the correct return statement. - const state = this._parser.parse(data); - this._parser.setState(state); + + // const state = this._parser.parse(data); + this._newParser.write(data); + // this._parser.setState(state); this.updateRange(this.buffer.y); this.refresh(this._refreshStart, this._refreshEnd); From 9b085d2bddaec865039d859b8364a12fcc92cc10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sat, 21 Apr 2018 05:12:24 +0200 Subject: [PATCH 02/44] new parser --- src/EscapeSequenceParser.test.ts | 1042 ++++++++++++++++++++++++++++++ src/EscapeSequenceParser.ts | 718 ++++++++++++++++++++ src/InputHandler.ts | 61 +- src/Terminal.ts | 15 +- 4 files changed, 1802 insertions(+), 34 deletions(-) create mode 100644 src/EscapeSequenceParser.test.ts create mode 100644 src/EscapeSequenceParser.ts diff --git a/src/EscapeSequenceParser.test.ts b/src/EscapeSequenceParser.test.ts new file mode 100644 index 0000000000..eefa760356 --- /dev/null +++ b/src/EscapeSequenceParser.test.ts @@ -0,0 +1,1042 @@ +import { AnsiParser, IParserTerminal } 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 + }, + inst_p: function (s: string, start: number, end: number): void { + this.calls.push(['print', s.substring(start, end)]); + }, + inst_o: function (s: string): void { + this.calls.push(['osc', s]); + }, + inst_x: function (flag: string): void { + this.calls.push(['exe', flag]); + }, + inst_c: function (collected: string, params: number[], flag: string): void { + this.calls.push(['csi', collected, params, flag]); + }, + inst_e: function (collected: string, flag: string): void { + this.calls.push(['esc', collected, flag]); + }, + inst_H: function (collected: string, params: number[], flag: string): void { + this.calls.push(['dcs hook', collected, params, flag]); + }, + inst_P: function (dcs: string): void { + this.calls.push(['dcs put', dcs]); + }, + inst_U: function (): void { + this.calls.push(['dcs unhook']); + } +}; + +let parser = new AnsiParser(testTerminal); + +describe('Parser init and methods', function(): void { + it('parser init', function (): void { + let p = new AnsiParser({}); + chai.expect(p.term).a('object'); + chai.expect(p.term.inst_p).a('function'); + chai.expect(p.term.inst_o).a('function'); + chai.expect(p.term.inst_x).a('function'); + chai.expect(p.term.inst_c).a('function'); + chai.expect(p.term.inst_e).a('function'); + chai.expect(p.term.inst_H).a('function'); + chai.expect(p.term.inst_P).a('function'); + chai.expect(p.term.inst_U).a('function'); + p.parse('\x1b[31mHello World!'); + }); + it('terminal callbacks', function (): void { + chai.expect(parser.term).equal(testTerminal); + chai.expect(parser.term.inst_p).equal(testTerminal.inst_p); + chai.expect(parser.term.inst_o).equal(testTerminal.inst_o); + chai.expect(parser.term.inst_x).equal(testTerminal.inst_x); + chai.expect(parser.term.inst_c).equal(testTerminal.inst_c); + chai.expect(parser.term.inst_e).equal(testTerminal.inst_e); + chai.expect(parser.term.inst_H).equal(testTerminal.inst_H); + chai.expect(parser.term.inst_P).equal(testTerminal.inst_P); + chai.expect(parser.term.inst_U).equal(testTerminal.inst_U); + }); + 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(0); + 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 = 0; + parser.parse(exes[i]); + chai.expect(parser.currentState).equal(0); + 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 = 0; + parser.parse(printables[i]); + chai.expect(parser.currentState).equal(0); + 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 (let state = 0; state < 14; ++state) { + for (let i = 0; i < exes.length; ++i) { + parser.currentState = state; + parser.parse(exes[i]); + chai.expect(parser.currentState).equal(0); + testTerminal.compare(((exceptions[state]) ? exceptions[state][exes[i]] : 0) || [['exe', exes[i]]]); + parser.reset(); + testTerminal.clear(); + } + parser.parse('\x9c'); + chai.expect(parser.currentState).equal(0); + testTerminal.compare([]); + parser.reset(); + testTerminal.clear(); + } + }); + it('trans ANYWHERE --> ESCAPE with clear', function (): void { + parser.reset(); + for (let state = 0; state < 14; ++state) { + parser.currentState = state; + parser.osc = '#'; + parser.params = [23]; + parser.collected = '#'; + parser.parse('\x1b'); + chai.expect(parser.currentState).equal(1); + 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 = 1; + parser.parse(exes[i]); + chai.expect(parser.currentState).equal(1); + testTerminal.compare([['exe', exes[i]]]); + parser.reset(); + testTerminal.clear(); + } + }); + it('state ESCAPE ignore', function (): void { + parser.reset(); + testTerminal.clear(); + parser.currentState = 1; + parser.parse('\x7f'); + chai.expect(parser.currentState).equal(1); + 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 = 1; + parser.parse(dispatches[i]); + chai.expect(parser.currentState).equal(0); + 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 = 1; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(2); + 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 = 2; + parser.parse(exes[i]); + chai.expect(parser.currentState).equal(2); + testTerminal.compare([['exe', exes[i]]]); + parser.reset(); + testTerminal.clear(); + } + }); + it('state ESCAPE_INTERMEDIATE ignore', function (): void { + parser.reset(); + testTerminal.clear(); + parser.currentState = 2; + parser.parse('\x7f'); + chai.expect(parser.currentState).equal(2); + 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 = 2; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(2); + 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 = 2; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(0); + testTerminal.compare([['esc', '', collect[i]]]); + parser.reset(); + testTerminal.clear(); + } + }); + it('trans ANYWHERE/ESCAPE --> CSI_ENTRY with clear', function (): void { + parser.reset(); + // C0 + parser.currentState = 1; + parser.osc = '#'; + parser.params = [123]; + parser.collected = '#'; + parser.parse('['); + chai.expect(parser.currentState).equal(3); + chai.expect(parser.osc).equal(''); + chai.expect(parser.params).eql([0]); + chai.expect(parser.collected).equal(''); + parser.reset(); + // C1 + for (let state = 0; state < 14; ++state) { + parser.currentState = state; + parser.osc = '#'; + parser.params = [123]; + parser.collected = '#'; + parser.parse('\x9b'); + chai.expect(parser.currentState).equal(3); + 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 = 3; + parser.parse(exes[i]); + chai.expect(parser.currentState).equal(3); + testTerminal.compare([['exe', exes[i]]]); + parser.reset(); + testTerminal.clear(); + } + }); + it('state CSI_ENTRY ignore', function (): void { + parser.reset(); + testTerminal.clear(); + parser.currentState = 3; + parser.parse('\x7f'); + chai.expect(parser.currentState).equal(3); + 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 = 3; + parser.parse(dispatches[i]); + chai.expect(parser.currentState).equal(0); + 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 = 3; + parser.parse(params[i]); + chai.expect(parser.currentState).equal(4); + chai.expect(parser.params).eql([params[i].charCodeAt(0) - 48]); + parser.reset(); + } + // ';' + parser.currentState = 3; + parser.parse('\x3b'); + chai.expect(parser.currentState).equal(4); + chai.expect(parser.params).eql([0, 0]); + parser.reset(); + for (let i = 0; i < collect.length; ++i) { + parser.currentState = 3; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(4); + 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 = 4; + parser.parse(exes[i]); + chai.expect(parser.currentState).equal(4); + 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 = 4; + parser.parse(params[i]); + chai.expect(parser.currentState).equal(4); + chai.expect(parser.params).eql([params[i].charCodeAt(0) - 48]); + parser.reset(); + } + parser.currentState = 4; + parser.parse('\x3b'); + chai.expect(parser.currentState).equal(4); + chai.expect(parser.params).eql([0, 0]); + parser.reset(); + }); + it('state CSI_PARAM ignore', function (): void { + parser.reset(); + testTerminal.clear(); + parser.currentState = 4; + parser.parse('\x7f'); + chai.expect(parser.currentState).equal(4); + 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 = 4; + parser.params = [0, 1]; + parser.parse(dispatches[i]); + chai.expect(parser.currentState).equal(0); + 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 = 3; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(5); + 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 = 4; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(5); + 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 = 5; + parser.parse(exes[i]); + chai.expect(parser.currentState).equal(5); + 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 = 5; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(5); + chai.expect(parser.collected).equal(collect[i]); + parser.reset(); + } + }); + it('state CSI_INTERMEDIATE ignore', function (): void { + parser.reset(); + testTerminal.clear(); + parser.currentState = 5; + parser.parse('\x7f'); + chai.expect(parser.currentState).equal(5); + 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 = 5; + parser.params = [0, 1]; + parser.parse(dispatches[i]); + chai.expect(parser.currentState).equal(0); + testTerminal.compare([['csi', '', [0, 1], dispatches[i]]]); + parser.reset(); + testTerminal.clear(); + } + }); + it('trans CSI_ENTRY --> CSI_IGNORE', function (): void { + parser.reset(); + parser.currentState = 3; + parser.parse('\x3a'); + chai.expect(parser.currentState).equal(6); + 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 = 4; + parser.parse('\x3b' + chars[i]); + chai.expect(parser.currentState).equal(6); + 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 = 5; + parser.parse(chars[i]); + chai.expect(parser.currentState).equal(6); + 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 = 6; + parser.parse(exes[i]); + chai.expect(parser.currentState).equal(6); + 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 = 6; + parser.parse(ignored[i]); + chai.expect(parser.currentState).equal(6); + 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 = 6; + parser.params = [0, 1]; + parser.parse(dispatches[i]); + chai.expect(parser.currentState).equal(0); + 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(7); + parser.reset(); + } + // C1 + for (let state = 0; state < 14; ++state) { + parser.currentState = state; + initializers = ['\x98', '\x9e', '\x9f']; + for (let i = 0; i < initializers.length; ++i) { + parser.parse(initializers[i]); + chai.expect(parser.currentState).equal(7); + 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 = 7; + parser.parse(ignored[i]); + chai.expect(parser.currentState).equal(7); + parser.reset(); + } + }); + it('trans ANYWHERE/ESCAPE --> OSC_STRING', function (): void { + parser.reset(); + // C0 + parser.parse('\x1b]'); + chai.expect(parser.currentState).equal(8); + parser.reset(); + // C1 + for (let state = 0; state < 14; ++state) { + parser.currentState = state; + parser.parse('\x9d'); + chai.expect(parser.currentState).equal(8); + 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 = 8; + parser.parse(ignored[i]); + chai.expect(parser.currentState).equal(8); + 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 = 8; + parser.parse(puts[i]); + chai.expect(parser.currentState).equal(8); + 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(9); + parser.reset(); + // C1 + for (let state = 0; state < 14; ++state) { + parser.currentState = state; + parser.parse('\x90'); + chai.expect(parser.currentState).equal(9); + 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 = 9; + parser.parse(ignored[i]); + chai.expect(parser.currentState).equal(9); + 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 = 9; + parser.parse(params[i]); + chai.expect(parser.currentState).equal(10); + chai.expect(parser.params).eql([params[i].charCodeAt(0) - 48]); + parser.reset(); + } + parser.currentState = 9; + parser.parse('\x3b'); + chai.expect(parser.currentState).equal(10); + chai.expect(parser.params).eql([0, 0]); + parser.reset(); + for (let i = 0; i < collect.length; ++i) { + parser.currentState = 9; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(10); + 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 = 10; + parser.parse(ignored[i]); + chai.expect(parser.currentState).equal(10); + 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 = 10; + parser.parse(params[i]); + chai.expect(parser.currentState).equal(10); + chai.expect(parser.params).eql([params[i].charCodeAt(0) - 48]); + parser.reset(); + } + parser.currentState = 10; + parser.parse('\x3b'); + chai.expect(parser.currentState).equal(10); + chai.expect(parser.params).eql([0, 0]); + parser.reset(); + }); + it('trans DCS_ENTRY --> DCS_IGNORE', function (): void { + parser.reset(); + parser.currentState = 9; + parser.parse('\x3a'); + chai.expect(parser.currentState).equal(11); + 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 = 10; + parser.parse('\x3b' + chars[i]); + chai.expect(parser.currentState).equal(11); + 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 = 12; + parser.parse(chars[i]); + chai.expect(parser.currentState).equal(11); + 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 = 11; + parser.parse(ignored[i]); + chai.expect(parser.currentState).equal(11); + 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 = 9; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(12); + 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 = 10; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(12); + 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 = 12; + parser.parse(ignored[i]); + chai.expect(parser.currentState).equal(12); + 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 = 12; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(12); + 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 = 12; + parser.parse('\x20' + chars[i]); + chai.expect(parser.currentState).equal(11); + 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 = 9; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(13); + 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 = 10; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(13); + 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 = 12; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(13); + 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 = 13; + parser.parse(puts[i]); + chai.expect(parser.currentState).equal(13); + testTerminal.compare([['dcs put', puts[i]]]); + parser.reset(); + testTerminal.clear(); + } + }); + it('state DCS_PASSTHROUGH ignore', function (): void { + parser.reset(); + testTerminal.clear(); + parser.currentState = 13; + parser.parse('\x7f'); + chai.expect(parser.currentState).equal(13); + 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 = 6; + parser.parse('€öäü'); + chai.expect(parser.currentState).equal(6); + testTerminal.compare([]); + parser.reset(); + testTerminal.clear(); + }); + it('DCS_IGNORE error', function (): void { + parser.reset(); + testTerminal.clear(); + parser.currentState = 11; + parser.parse('€öäü'); + chai.expect(parser.currentState).equal(11); + testTerminal.compare([]); + parser.reset(); + testTerminal.clear(); + }); + it('DCS_PASSTHROUGH error', function (): void { + parser.reset(); + testTerminal.clear(); + parser.currentState = 13; + parser.parse('€öäü'); + chai.expect(parser.currentState).equal(13); + testTerminal.compare([['dcs put', '€öäü']]); + parser.reset(); + testTerminal.clear(); + }); + it('error else of if (code > 159)', function (): void { + parser.reset(); + testTerminal.clear(); + parser.currentState = 0; + parser.parse('\x1e'); + chai.expect(parser.currentState).equal(0); + testTerminal.compare([]); + parser.reset(); + testTerminal.clear(); + }); +}); + +let errorTerminal1 = function(): void {}; +errorTerminal1.prototype = testTerminal; +let errTerminal1 = new errorTerminal1(); +errTerminal1.inst_E = function(e: any): void { + this.calls.push(['error', e]); +}; +let errParser1 = new AnsiParser(errTerminal1); + +let errorTerminal2 = function(): void {}; +errorTerminal2.prototype = testTerminal; +let errTerminal2 = new errorTerminal2(); +errTerminal2.inst_E = function(e: any): any { + this.calls.push(['error', e]); + return true; // --> abort parsing +}; +let errParser2 = new AnsiParser(errTerminal2); + +describe('error tests', function(): void { + it('CSI_PARAM unicode error - inst_E output w/o abort', function (): void { + errParser1.parse('\x1b[<31;5€normal print'); + errTerminal1.compare([ + ['error', { + pos: 7, + character: '€', + state: 4, + print: -1, + dcs: -1, + osc: '', + collect: '<', + params: [31, 5]}], + ['print', 'normal print'] + ]); + parser.reset(); + testTerminal.clear(); + }); + it('CSI_PARAM unicode error - inst_E output with abort', function (): void { + errParser2.parse('\x1b[<31;5€no print'); + errTerminal2.compare([ + ['error', { + pos: 7, + character: '€', + state: 4, + print: -1, + dcs: -1, + osc: '', + collect: '<', + params: [31, 5]}] + ]); + parser.reset(); + testTerminal.clear(); + }); +}); diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts new file mode 100644 index 0000000000..6facddeab8 --- /dev/null +++ b/src/EscapeSequenceParser.ts @@ -0,0 +1,718 @@ +export interface IParserTerminal { + inst_p?: (s: string, start: number, end: number) => void; + inst_o?: (s: string) => void; + inst_x?: (flag: string) => void; + inst_c?: (collected: string, params: number[], flag: string) => void; + inst_e?: (collected: string, flag: string) => void; + inst_H?: (collected: string, params: number[], flag: string) => void; + inst_P?: (dcs: string) => void; + inst_U?: () => void; + inst_E?: () => void; // TODO: real signature +} + + +export function r(a: number, b: number): number[] { + let c = b - a; + let arr = new Array(c); + while (c--) { + arr[c] = --b; + } + return arr; +} + + +export class TransitionTable { + public table: Uint8Array; + constructor(length: number) { + this.table = new Uint8Array(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_list(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); + } + } +} + + +let PRINTABLES = r(0x20, 0x7f); +let EXECUTABLES = r(0x00, 0x18); +EXECUTABLES.push(0x19); +EXECUTABLES.concat(r(0x1c, 0x20)); + + +export const TRANSITION_TABLE = (function (): TransitionTable { + let t: TransitionTable = new TransitionTable(4095); + + // table with default transition [any] --> [error, GROUND] + for (let state = 0; state < 14; ++state) { + for (let code = 0; code < 160; ++code) { + t[state << 8 | code] = 16; + } + } + + // apply transitions + // printables + t.add_list(PRINTABLES, 0, 2, 0); + // global anywhere rules + for (let state = 0; state < 14; ++state) { + t.add_list([0x18, 0x1a, 0x99, 0x9a], state, 3, 0); + t.add_list(r(0x80, 0x90), state, 3, 0); + t.add_list(r(0x90, 0x98), state, 3, 0); + t.add(0x9c, state, 0, 0); // ST as terminator + t.add(0x1b, state, 11, 1); // ESC + t.add(0x9d, state, 4, 8); // OSC + t.add_list([0x98, 0x9e, 0x9f], state, 0, 7); + t.add(0x9b, state, 11, 3); // CSI + t.add(0x90, state, 11, 9); // DCS + } + // rules for executables and 7f + t.add_list(EXECUTABLES, 0, 3, 0); + t.add_list(EXECUTABLES, 1, 3, 1); + t.add(0x7f, 1, null, 1); + t.add_list(EXECUTABLES, 8, null, 8); + t.add_list(EXECUTABLES, 3, 3, 3); + t.add(0x7f, 3, null, 3); + t.add_list(EXECUTABLES, 4, 3, 4); + t.add(0x7f, 4, null, 4); + t.add_list(EXECUTABLES, 6, 3, 6); + t.add_list(EXECUTABLES, 5, 3, 5); + t.add(0x7f, 5, null, 5); + t.add_list(EXECUTABLES, 2, 3, 2); + t.add(0x7f, 2, null, 2); + // osc + t.add(0x5d, 1, 4, 8); + t.add_list(PRINTABLES, 8, 5, 8); + t.add(0x7f, 8, 5, 8); + t.add_list([0x9c, 0x1b, 0x18, 0x1a, 0x07], 8, 6, 0); + t.add_list(r(0x1c, 0x20), 8, 0, 8); + // sos/pm/apc does nothing + t.add_list([0x58, 0x5e, 0x5f], 1, 0, 7); + t.add_list(PRINTABLES, 7, null, 7); + t.add_list(EXECUTABLES, 7, null, 7); + t.add(0x9c, 7, 0, 0); + // csi entries + t.add(0x5b, 1, 11, 3); + t.add_list(r(0x40, 0x7f), 3, 7, 0); + t.add_list(r(0x30, 0x3a), 3, 8, 4); + t.add(0x3b, 3, 8, 4); + t.add_list([0x3c, 0x3d, 0x3e, 0x3f], 3, 9, 4); + t.add_list(r(0x30, 0x3a), 4, 8, 4); + t.add(0x3b, 4, 8, 4); + t.add_list(r(0x40, 0x7f), 4, 7, 0); + t.add_list([0x3a, 0x3c, 0x3d, 0x3e, 0x3f], 4, 0, 6); + t.add_list(r(0x20, 0x40), 6, null, 6); + t.add(0x7f, 6, null, 6); + t.add_list(r(0x40, 0x7f), 6, 0, 0); + t.add(0x3a, 3, 0, 6); + t.add_list(r(0x20, 0x30), 3, 9, 5); + t.add_list(r(0x20, 0x30), 5, 9, 5); + t.add_list(r(0x30, 0x40), 5, 0, 6); + t.add_list(r(0x40, 0x7f), 5, 7, 0); + t.add_list(r(0x20, 0x30), 4, 9, 5); + // esc_intermediate + t.add_list(r(0x20, 0x30), 1, 9, 2); + t.add_list(r(0x20, 0x30), 2, 9, 2); + t.add_list(r(0x30, 0x7f), 2, 10, 0); + t.add_list(r(0x30, 0x50), 1, 10, 0); + t.add_list([0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x59, 0x5a, 0x5c], 1, 10, 0); + t.add_list(r(0x60, 0x7f), 1, 10, 0); + // dcs entry + t.add(0x50, 1, 11, 9); + t.add_list(EXECUTABLES, 9, null, 9); + t.add(0x7f, 9, null, 9); + t.add_list(r(0x1c, 0x20), 9, null, 9); + t.add_list(r(0x20, 0x30), 9, 9, 12); + t.add(0x3a, 9, 0, 11); + t.add_list(r(0x30, 0x3a), 9, 8, 10); + t.add(0x3b, 9, 8, 10); + t.add_list([0x3c, 0x3d, 0x3e, 0x3f], 9, 9, 10); + t.add_list(EXECUTABLES, 11, null, 11); + t.add_list(r(0x20, 0x80), 11, null, 11); + t.add_list(r(0x1c, 0x20), 11, null, 11); + t.add_list(EXECUTABLES, 10, null, 10); + t.add(0x7f, 10, null, 10); + t.add_list(r(0x1c, 0x20), 10, null, 10); + t.add_list(r(0x30, 0x3a), 10, 8, 10); + t.add(0x3b, 10, 8, 10); + t.add_list([0x3a, 0x3c, 0x3d, 0x3e, 0x3f], 10, 0, 11); + t.add_list(r(0x20, 0x30), 10, 9, 12); + t.add_list(EXECUTABLES, 12, null, 12); + t.add(0x7f, 12, null, 12); + t.add_list(r(0x1c, 0x20), 12, null, 12); + t.add_list(r(0x20, 0x30), 12, 9, 12); + t.add_list(r(0x30, 0x40), 12, 0, 11); + t.add_list(r(0x40, 0x7f), 12, 12, 13); + t.add_list(r(0x40, 0x7f), 10, 12, 13); + t.add_list(r(0x40, 0x7f), 9, 12, 13); + t.add_list(EXECUTABLES, 13, 13, 13); + t.add_list(PRINTABLES, 13, 13, 13); + t.add(0x7f, 13, null, 13); + t.add_list([0x1b, 0x9c], 13, 14, 0); + + return t; +})(); + +export class AnsiParser { + public initialState: number; + public currentState: number; + public transitions: TransitionTable; + public osc: string; + public params: number[]; + public collected: string; + public term: any; + constructor(terminal: IParserTerminal) { + this.initialState = 0; + this.currentState = this.initialState | 0; + this.transitions = new TransitionTable(4095); + this.transitions.table.set(TRANSITION_TABLE.table); + this.osc = ''; + this.params = [0]; + this.collected = ''; + this.term = terminal || {}; + let instructions = ['inst_p', 'inst_o', 'inst_x', 'inst_c', + 'inst_e', 'inst_H', 'inst_P', 'inst_U', 'inst_E']; + for (let i = 0; i < instructions.length; ++i) { + if (!(instructions[i] in this.term)) { + this.term[instructions[i]] = function(): void {}; + } + } + } + reset(): void { + this.currentState = this.initialState; + this.osc = ''; + this.params = [0]; + this.collected = ''; + } + parse(s: string): void { + let code = 0; + let transition = 0; + let error = false; + let currentState = this.currentState; + + // local buffers + let printed = -1; + let dcs = -1; + let osc = this.osc; + let collected = this.collected; + let params = this.params; + let table: Uint8Array = this.transitions.table; + + // process input string + let l = s.length; + for (let i = 0; i < l; ++i) { + code = s.charCodeAt(i); + // shortcut for most chars (print action) + if (currentState === 0 && (code > 0x1f && code < 0x80)) { + printed = (~printed) ? printed : i; + continue; + } + if (currentState === 4) { + if (code === 0x3b) { + params.push(0); + continue; + } + if (code > 0x2f && code < 0x39) { + params[params.length - 1] = params[params.length - 1] * 10 + code - 48; + continue; + } + } + transition = ((code < 0xa0) ? (table[currentState << 8 | code]) : 16); + switch (transition >> 4) { + case 2: // print + printed = (~printed) ? printed : i; + break; + case 3: // execute + if (printed + 1) { + this.term.inst_p(s, printed, i); + printed = -1; + } + this.term.inst_x(String.fromCharCode(code)); + break; + case 0: // ignore + // handle leftover print and dcs chars + if (printed + 1) { + this.term.inst_p(s, printed, i); + printed = -1; + } else if (dcs + 1) { + this.term.inst_P(s.substring(dcs, i)); + dcs = -1; + } + break; + case 1: // error + // handle unicode chars in write buffers w'o state change + if (code > 0x9f) { + switch (currentState) { + case 0: // GROUND -> add char to print string + printed = (~printed) ? printed : i; + break; + case 8: // OSC_STRING -> add char to osc string + osc += String.fromCharCode(code); + transition |= 8; + break; + case 6: // CSI_IGNORE -> ignore char + transition |= 6; + break; + case 11: // DCS_IGNORE -> ignore char + transition |= 11; + break; + case 13: // DCS_PASSTHROUGH -> add char to dcs + if (!(~dcs)) dcs = i | 0; + transition |= 13; + break; + default: // real error + error = true; + } + } else { // real error + error = true; + } + if (error) { + if (this.term.inst_E( + { + pos: i, // position in parse string + character: String.fromCharCode(code), // wrong character + state: currentState, // in state + print: printed, // print buffer + dcs: dcs, // dcs buffer + osc: osc, // osc buffer + collect: collected, // collect buffer + params: params // params buffer + })) { + return; + } + error = false; + } + break; + case 7: // csi_dispatch + this.term.inst_c(collected, params, String.fromCharCode(code)); + break; + case 8: // param + if (code === 0x3b) params.push(0); + else params[params.length - 1] = params[params.length - 1] * 10 + code - 48; + break; + case 9: // collect + collected += String.fromCharCode(code); + break; + case 10: // esc_dispatch + this.term.inst_e(collected, String.fromCharCode(code)); + break; + case 11: // clear + if (~printed) { + this.term.inst_p(s, printed, i); + printed = -1; + } + osc = ''; + params = [0]; + collected = ''; + dcs = -1; + break; + case 12: // dcs_hook + this.term.inst_H(collected, params, String.fromCharCode(code)); + break; + case 13: // dcs_put + if (!(~dcs)) dcs = i; + break; + case 14: // dcs_unhook + if (~dcs) this.term.inst_P(s.substring(dcs, i)); + this.term.inst_U(); + if (code === 0x1b) transition |= 1; + osc = ''; + params = [0]; + collected = ''; + dcs = -1; + break; + case 4: // osc_start + if (~printed) { + this.term.inst_p(s, printed, i); + printed = -1; + } + osc = ''; + break; + case 5: // osc_put + osc += s.charAt(i); + break; + case 6: // osc_end + if (osc && code !== 0x18 && code !== 0x1a) this.term.inst_o(osc); + if (code === 0x1b) transition |= 1; + osc = ''; + params = [0]; + collected = ''; + dcs = -1; + break; + } + currentState = transition & 15; + } + + // push leftover pushable buffers to terminal + if (!currentState && (printed + 1)) { + this.term.inst_p(s, printed, s.length); + } else if (currentState === 13 && (dcs + 1)) { + this.term.inst_P(s.substring(dcs)); + } + + // save non pushable buffers + this.osc = osc; + this.collected = collected; + this.params = params; + + // save state + this.currentState = currentState; + } +} + + + + + +import { IInputHandler, IInputHandlingTerminal } from './Types'; +import { CHARSETS, DEFAULT_CHARSET } from './Charsets'; +import { C0 } from './EscapeSequences'; + +// glue code between AnsiParser and Terminal +export class ParserTerminal implements IParserTerminal { + private _parser: AnsiParser; + private _terminal: any; + private _inputHandler: IInputHandler; + + constructor(_terminal: any, _inputHandler: IInputHandler) { + this._parser = new AnsiParser(this); + this._terminal = _terminal; + this._inputHandler = _inputHandler; + } + + write(data: string): void { + const cursorStartX = this._terminal.buffer.x; + const cursorStartY = this._terminal.buffer.y; + if (this._terminal.debug) { + this._terminal.log('data: ' + data); + } + // apply leftover surrogate high from last write + if (this._terminal.surrogate_high) { + data = this._terminal.surrogate_high + data; + this._terminal.surrogate_high = ''; + } + + this._parser.parse(data); + + if (this._terminal.buffer.x !== cursorStartX || this._terminal.buffer.y !== cursorStartY) { + this._terminal.emit('cursormove'); + } + } + + inst_p(data: string, start: number, end: number): void { + // const l = data.length; + let ch; + let code; + let low; + for (let i = start; i < end; ++i) { + ch = data.charAt(i); + code = data.charCodeAt(i); + if (0xD800 <= code && code <= 0xDBFF) { + // we got a surrogate high + // get surrogate low (next 2 bytes) + low = data.charCodeAt(i + 1); + if (isNaN(low)) { + // end of data stream, save surrogate high + this._terminal.surrogate_high = ch; + continue; + } + code = ((code - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000; + ch += data.charAt(i + 1); + } + // surrogate low - already handled above + if (0xDC00 <= code && code <= 0xDFFF) { + continue; + } + this._inputHandler.addChar(ch, code); + } + } + + inst_o(data: string): void { + let params = data.split(';'); + switch (parseInt(params[0])) { + case 0: + case 1: + case 2: + if (params[1]) { + this._terminal.title = params[1]; + 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; + } + } + + inst_x(flag: string): void { + switch (flag) { + case C0.BEL: return this._inputHandler.bell(); + case C0.LF: return this._inputHandler.lineFeed(); + case C0.VT: return this._inputHandler.lineFeed(); + 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)); + } + this._terminal.error('Unknown EXEC flag: %s.', flag); + } + + inst_c(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': + 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); + } + + inst_e(collected: string, flag: string): void { + let cs; + + switch (collected) { + case '': + switch (flag) { + // case '6': // Back Index (DECBI), VT420 and up - not supported + case '7': // Save Cursor (DECSC) + this._inputHandler.saveCursor(); + return; + case '8': // Restore Cursor (DECRC) + this._inputHandler.restoreCursor(); + return; + // case '9': // Forward Index (DECFI), VT420 and up - not supported + case 'D': // Index (IND is 0x84) + this._terminal.index(); + return; + 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) + (this._terminal).tabSet(); + return; + case 'M': // Reverse Index (RI is 0x8d) + this._terminal.reverseIndex(); + return; + 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). + this._terminal.setgLevel(2); + return; + case 'o': // Invoke the G3 Character Set as GL (LS3). + this._terminal.setgLevel(3); + return; + case '|': // Invoke the G3 Character Set as GR (LS3R). + this._terminal.setgLevel(3); + return; + case '}': // Invoke the G2 Character Set as GR (LS2R). + this._terminal.setgLevel(2); + return; + case '~': // Invoke the G1 Character Set as GR (LS1R). + this._terminal.setgLevel(1); + return; + } + // 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) + cs = CHARSETS[flag]; + if (!cs) cs = DEFAULT_CHARSET; + this._terminal.setgCharset(0, cs); + return; + case ')': // G1 (VT100) + cs = CHARSETS[flag]; + if (!cs) cs = DEFAULT_CHARSET; + this._terminal.setgCharset(1, cs); + return; + case '*': // G2 (VT220) + cs = CHARSETS[flag]; + if (!cs) cs = DEFAULT_CHARSET; + this._terminal.setgCharset(2, cs); + return; + case '+': // G3 (VT220) + cs = CHARSETS[flag]; + if (!cs) cs = DEFAULT_CHARSET; + this._terminal.setgCharset(3, cs); + return; + case '-': // G1 (VT300) + cs = CHARSETS[flag]; + if (!cs) cs = DEFAULT_CHARSET; + this._terminal.setgCharset(1, cs); + return; + case '.': // G2 (VT300) + if (!cs) cs = DEFAULT_CHARSET; + this._terminal.setgCharset(2, cs); + return; + case '/': // G3 (VT300) + // not supported - how to deal with this? (original code is not reachable) + return; + default: + this._terminal.error('Unknown ESC control: %s %s.', collected, flag); + } + } + + inst_H(collected: string, params: number[], flag: string): void { + // TODO + } + + inst_P(dcs: string): void { + // TODO + } + + inst_U(): void { + // TODO + } + + inst_E(): void { + // TODO + } +} diff --git a/src/InputHandler.ts b/src/InputHandler.ts index acf7af1fd0..3d29f7b42d 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -22,11 +22,14 @@ export class InputHandler implements IInputHandler { constructor(private _terminal: IInputHandlingTerminal) { } public addChar(char: string, code: number): void { - if (char >= ' ') { + // if (char >= ' ') { // calculate print space // expensive call, therefore we save width in line buffer const chWidth = wcwidth(code); + // localize buffer + let buffer = this._terminal.buffer; + if (this._terminal.charset && this._terminal.charset[char]) { char = this._terminal.charset[char]; } @@ -35,42 +38,42 @@ export class InputHandler implements IInputHandler { this._terminal.emit('a11y.char', char); } - let row = this._terminal.buffer.y + this._terminal.buffer.ybase; + let row = buffer.y + buffer.ybase; // insert combining char in last cell // FIXME: needs handling after cursor jumps - if (!chWidth && this._terminal.buffer.x) { + if (!chWidth && buffer.x) { // dont overflow left - if (this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1]) { - if (!this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1][CHAR_DATA_WIDTH_INDEX]) { + if (buffer.lines.get(row)[buffer.x - 1]) { + if (!buffer.lines.get(row)[buffer.x - 1][CHAR_DATA_WIDTH_INDEX]) { // found empty cell after fullwidth, need to go 2 cells back - if (this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 2]) { - this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 2][CHAR_DATA_CHAR_INDEX] += char; - this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 2][3] = char.charCodeAt(0); + if (buffer.lines.get(row)[buffer.x - 2]) { + buffer.lines.get(row)[buffer.x - 2][CHAR_DATA_CHAR_INDEX] += char; + buffer.lines.get(row)[buffer.x - 2][3] = char.charCodeAt(0); } } else { - this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1][CHAR_DATA_CHAR_INDEX] += char; - this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1][3] = char.charCodeAt(0); + buffer.lines.get(row)[buffer.x - 1][CHAR_DATA_CHAR_INDEX] += char; + buffer.lines.get(row)[buffer.x - 1][3] = char.charCodeAt(0); } - this._terminal.updateRange(this._terminal.buffer.y); + this._terminal.updateRange(buffer.y); } return; } // goto next line if ch would overflow // TODO: needs a global min terminal width of 2 - if (this._terminal.buffer.x + chWidth - 1 >= this._terminal.cols) { + if (buffer.x + chWidth - 1 >= this._terminal.cols) { // autowrap - DECAWM if (this._terminal.wraparoundMode) { - this._terminal.buffer.x = 0; - this._terminal.buffer.y++; - if (this._terminal.buffer.y > this._terminal.buffer.scrollBottom) { - this._terminal.buffer.y--; + buffer.x = 0; + buffer.y++; + if (buffer.y > buffer.scrollBottom) { + buffer.y--; this._terminal.scroll(true); } else { // The line already exists (eg. the initial viewport), mark it as a // wrapped line - (this._terminal.buffer.lines.get(this._terminal.buffer.y)).isWrapped = true; + (buffer.lines.get(buffer.y)).isWrapped = true; } } else { if (chWidth === 2) { // FIXME: check for xterm behavior @@ -78,7 +81,7 @@ export class InputHandler implements IInputHandler { } } } - row = this._terminal.buffer.y + this._terminal.buffer.ybase; + row = buffer.y + buffer.ybase; // insert mode: move characters to right if (this._terminal.insertMode) { @@ -86,28 +89,28 @@ export class InputHandler implements IInputHandler { for (let moves = 0; moves < chWidth; ++moves) { // remove last cell, if it's width is 0 // we have to adjust the second last cell as well - const removed = this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).pop(); + const removed = buffer.lines.get(buffer.y + buffer.ybase).pop(); if (removed[CHAR_DATA_WIDTH_INDEX] === 0 - && this._terminal.buffer.lines.get(row)[this._terminal.cols - 2] - && this._terminal.buffer.lines.get(row)[this._terminal.cols - 2][CHAR_DATA_WIDTH_INDEX] === 2) { - this._terminal.buffer.lines.get(row)[this._terminal.cols - 2] = [this._terminal.curAttr, ' ', 1, ' '.charCodeAt(0)]; + && buffer.lines.get(row)[this._terminal.cols - 2] + && buffer.lines.get(row)[this._terminal.cols - 2][CHAR_DATA_WIDTH_INDEX] === 2) { + buffer.lines.get(row)[this._terminal.cols - 2] = [this._terminal.curAttr, ' ', 1, ' '.charCodeAt(0)]; } // insert empty cell at cursor - this._terminal.buffer.lines.get(row).splice(this._terminal.buffer.x, 0, [this._terminal.curAttr, ' ', 1, ' '.charCodeAt(0)]); + buffer.lines.get(row).splice(buffer.x, 0, [this._terminal.curAttr, ' ', 1, ' '.charCodeAt(0)]); } } - this._terminal.buffer.lines.get(row)[this._terminal.buffer.x] = [this._terminal.curAttr, char, chWidth, char.charCodeAt(0)]; - this._terminal.buffer.x++; - this._terminal.updateRange(this._terminal.buffer.y); + buffer.lines.get(row)[buffer.x] = [this._terminal.curAttr, char, chWidth, char.charCodeAt(0)]; + buffer.x++; + this._terminal.updateRange(buffer.y); // fullwidth char - set next cell width to zero and advance cursor if (chWidth === 2) { - this._terminal.buffer.lines.get(row)[this._terminal.buffer.x] = [this._terminal.curAttr, '', 0, undefined]; - this._terminal.buffer.x++; + buffer.lines.get(row)[buffer.x] = [this._terminal.curAttr, '', 0, undefined]; + buffer.x++; } - } + // } } /** diff --git a/src/Terminal.ts b/src/Terminal.ts index ba1fff3df1..44c9064333 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -32,7 +32,7 @@ import { Viewport } from './Viewport'; import { rightClickHandler, moveTextAreaUnderMouseCursor, pasteHandler, copyHandler } from './handlers/Clipboard'; import { C0 } from './EscapeSequences'; import { InputHandler } from './InputHandler'; -import { Parser } from './Parser'; +// import { Parser } from './Parser'; import { Renderer } from './renderer/Renderer'; import { Linkifier } from './Linkifier'; import { SelectionManager } from './SelectionManager'; @@ -49,6 +49,7 @@ import { AccessibilityManager } from './AccessibilityManager'; import { ScreenDprMonitor } from './utils/ScreenDprMonitor'; import { ITheme, ILocalizableStrings, IMarker, IDisposable } from 'xterm'; import { removeTerminalFromCache } from './renderer/atlas/CharAtlas'; +import { ParserTerminal } from './EscapeSequenceParser'; // reg + shift key mappings for digits and special chars const KEYCODE_KEY_MAPPINGS = { @@ -216,7 +217,8 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II private _inputHandler: InputHandler; public soundManager: SoundManager; - private _parser: Parser; + // private _parser: Parser; + private _newParser: ParserTerminal; public renderer: IRenderer; public selectionManager: SelectionManager; public linkifier: ILinkifier; @@ -331,7 +333,8 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this._userScrolling = false; this._inputHandler = new InputHandler(this); - this._parser = new Parser(this._inputHandler, this); + // this._parser = new Parser(this._inputHandler, this); + this._newParser = new ParserTerminal(this, this._inputHandler); // Reuse renderer if the Terminal is being recreated via a reset call. this.renderer = this.renderer || null; this.selectionManager = this.selectionManager || null; @@ -1315,8 +1318,10 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // middle of parsing escape sequence in two chunks. For some reason the // state of the parser resets to 0 after exiting parser.parse. This change // just sets the state back based on the correct return statement. - const state = this._parser.parse(data); - this._parser.setState(state); + + // const state = this._parser.parse(data); + this._newParser.write(data); + // this._parser.setState(state); this.updateRange(this.buffer.y); this.refresh(this._refreshStart, this._refreshEnd); From 20760d09e43659e7231bd674b5bcb9104e0d442b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 22 Apr 2018 04:34:42 +0200 Subject: [PATCH 03/44] utilize more typescript features --- src/EscapeSequenceParser.test.ts | 1817 +++++++++++++++--------------- src/EscapeSequenceParser.ts | 577 +++++----- src/Terminal.ts | 4 +- 3 files changed, 1243 insertions(+), 1155 deletions(-) diff --git a/src/EscapeSequenceParser.test.ts b/src/EscapeSequenceParser.test.ts index eefa760356..296ac34d4f 100644 --- a/src/EscapeSequenceParser.test.ts +++ b/src/EscapeSequenceParser.test.ts @@ -1,4 +1,4 @@ -import { AnsiParser, IParserTerminal } from './EscapeSequenceParser'; +import { EscapeSequenceParser, IParserTerminal, STATE } from './EscapeSequenceParser'; import * as chai from 'chai'; function r(a: number, b: number): string[] { @@ -24,1019 +24,1040 @@ let testTerminal: ITestTerminal = { compare: function (value: any): void { chai.expect(this.calls.slice()).eql(value); // weird bug w'o slicing here }, - inst_p: function (s: string, start: number, end: number): void { - this.calls.push(['print', s.substring(start, end)]); + actionPrint: function (data: string, start: number, end: number): void { + this.calls.push(['print', data.substring(start, end)]); }, - inst_o: function (s: string): void { + actionOSC: function (s: string): void { this.calls.push(['osc', s]); }, - inst_x: function (flag: string): void { + actionExecute: function (flag: string): void { this.calls.push(['exe', flag]); }, - inst_c: function (collected: string, params: number[], flag: string): void { + actionCSI: function (collected: string, params: number[], flag: string): void { this.calls.push(['csi', collected, params, flag]); }, - inst_e: function (collected: string, flag: string): void { + actionESC: function (collected: string, flag: string): void { this.calls.push(['esc', collected, flag]); }, - inst_H: function (collected: string, params: number[], flag: string): void { + actionDCSHook: function (collected: string, params: number[], flag: string): void { this.calls.push(['dcs hook', collected, params, flag]); }, - inst_P: function (dcs: string): void { - this.calls.push(['dcs put', dcs]); + actionDCSPrint: function (data: string, start: number, end: number): void { + this.calls.push(['dcs put', data.substring(start, end)]); }, - inst_U: function (): void { + actionDCSUnhook: function (): void { this.calls.push(['dcs unhook']); } }; -let parser = new AnsiParser(testTerminal); +let states: number[] = [ + STATE.GROUND, + STATE.ESCAPE, + STATE.ESCAPE_INTERMEDIATE, + STATE.CSI_ENTRY, + STATE.CSI_PARAM, + STATE.CSI_INTERMEDIATE, + STATE.CSI_IGNORE, + STATE.SOS_PM_APC_STRING, + STATE.OSC_STRING, + STATE.DCS_ENTRY, + STATE.DCS_PARAM, + STATE.DCS_IGNORE, + STATE.DCS_INTERMEDIATE, + STATE.DCS_PASSTHROUGH +]; +let state: any; -describe('Parser init and methods', function(): void { - it('parser init', function (): void { - let p = new AnsiParser({}); - chai.expect(p.term).a('object'); - chai.expect(p.term.inst_p).a('function'); - chai.expect(p.term.inst_o).a('function'); - chai.expect(p.term.inst_x).a('function'); - chai.expect(p.term.inst_c).a('function'); - chai.expect(p.term.inst_e).a('function'); - chai.expect(p.term.inst_H).a('function'); - chai.expect(p.term.inst_P).a('function'); - chai.expect(p.term.inst_U).a('function'); - p.parse('\x1b[31mHello World!'); - }); - it('terminal callbacks', function (): void { - chai.expect(parser.term).equal(testTerminal); - chai.expect(parser.term.inst_p).equal(testTerminal.inst_p); - chai.expect(parser.term.inst_o).equal(testTerminal.inst_o); - chai.expect(parser.term.inst_x).equal(testTerminal.inst_x); - chai.expect(parser.term.inst_c).equal(testTerminal.inst_c); - chai.expect(parser.term.inst_e).equal(testTerminal.inst_e); - chai.expect(parser.term.inst_H).equal(testTerminal.inst_H); - chai.expect(parser.term.inst_P).equal(testTerminal.inst_P); - chai.expect(parser.term.inst_U).equal(testTerminal.inst_U); - }); - 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 = '#'; +let parser = new EscapeSequenceParser(testTerminal); - parser.reset(); - chai.expect(parser.currentState).equal(0); - chai.expect(parser.osc).equal(''); - chai.expect(parser.params).eql([0]); - chai.expect(parser.collected).equal(''); - }); -}); +describe('EscapeSequenceParser', function(): void { -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 = 0; - parser.parse(exes[i]); + 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.actionPrint); + 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); - testTerminal.compare([['exe', exes[i]]]); + 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(); - testTerminal.clear(); - } + chai.expect(parser.currentState).equal(STATE.GROUND); + chai.expect(parser.osc).equal(''); + chai.expect(parser.params).eql([0]); + chai.expect(parser.collected).equal(''); + }); }); - 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 = 0; - parser.parse(printables[i]); - chai.expect(parser.currentState).equal(0); - testTerminal.compare([['print', printables[i]]]); + + describe('state transitions and actions', function(): void { + it('state GROUND execute action', function (): void { 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 (let state = 0; state < 14; ++state) { + let exes = r(0x00, 0x18); + exes.concat(['\x19']); + exes.concat(r(0x1c, 0x20)); for (let i = 0; i < exes.length; ++i) { + parser.currentState = STATE.GROUND; + parser.parse(exes[i]); + chai.expect(parser.currentState).equal(STATE.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 = STATE.GROUND; + parser.parse(printables[i]); + chai.expect(parser.currentState).equal(STATE.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(STATE.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(STATE.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(STATE.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 = STATE.ESCAPE; parser.parse(exes[i]); - chai.expect(parser.currentState).equal(0); - testTerminal.compare(((exceptions[state]) ? exceptions[state][exes[i]] : 0) || [['exe', exes[i]]]); + chai.expect(parser.currentState).equal(STATE.ESCAPE); + testTerminal.compare([['exe', exes[i]]]); parser.reset(); testTerminal.clear(); } - parser.parse('\x9c'); - chai.expect(parser.currentState).equal(0); - testTerminal.compare([]); + }); + it('state ESCAPE ignore', function (): void { parser.reset(); testTerminal.clear(); - } - }); - it('trans ANYWHERE --> ESCAPE with clear', function (): void { - parser.reset(); - for (let state = 0; state < 14; ++state) { - parser.currentState = state; - parser.osc = '#'; - parser.params = [23]; - parser.collected = '#'; - parser.parse('\x1b'); - chai.expect(parser.currentState).equal(1); - chai.expect(parser.osc).equal(''); - chai.expect(parser.params).eql([0]); - chai.expect(parser.collected).equal(''); + parser.currentState = STATE.ESCAPE; + parser.parse('\x7f'); + chai.expect(parser.currentState).equal(STATE.ESCAPE); + testTerminal.compare([]); 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 = 1; - parser.parse(exes[i]); - chai.expect(parser.currentState).equal(1); - testTerminal.compare([['exe', exes[i]]]); + testTerminal.clear(); + }); + it('trans ESCAPE --> GROUND with ecs_dispatch action', function (): void { parser.reset(); testTerminal.clear(); - } - }); - it('state ESCAPE ignore', function (): void { - parser.reset(); - testTerminal.clear(); - parser.currentState = 1; - parser.parse('\x7f'); - chai.expect(parser.currentState).equal(1); - 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 = 1; - parser.parse(dispatches[i]); - chai.expect(parser.currentState).equal(0); - testTerminal.compare([['esc', '', dispatches[i]]]); + 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 = STATE.ESCAPE; + parser.parse(dispatches[i]); + chai.expect(parser.currentState).equal(STATE.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 = STATE.ESCAPE; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(STATE.ESCAPE_INTERMEDIATE); + chai.expect(parser.collected).equal(collect[i]); + parser.reset(); + } + }); + it('state ESCAPE_INTERMEDIATE execute rules', function (): void { 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 = 1; - parser.parse(collect[i]); - chai.expect(parser.currentState).equal(2); - chai.expect(parser.collected).equal(collect[i]); + let exes = r(0x00, 0x18); + exes.concat(['\x19']); + exes.concat(r(0x1c, 0x20)); + for (let i = 0; i < exes.length; ++i) { + parser.currentState = STATE.ESCAPE_INTERMEDIATE; + parser.parse(exes[i]); + chai.expect(parser.currentState).equal(STATE.ESCAPE_INTERMEDIATE); + testTerminal.compare([['exe', exes[i]]]); + parser.reset(); + testTerminal.clear(); + } + }); + it('state ESCAPE_INTERMEDIATE ignore', function (): void { 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 = 2; - parser.parse(exes[i]); - chai.expect(parser.currentState).equal(2); - testTerminal.compare([['exe', exes[i]]]); + testTerminal.clear(); + parser.currentState = STATE.ESCAPE_INTERMEDIATE; + parser.parse('\x7f'); + chai.expect(parser.currentState).equal(STATE.ESCAPE_INTERMEDIATE); + testTerminal.compare([]); parser.reset(); testTerminal.clear(); - } - }); - it('state ESCAPE_INTERMEDIATE ignore', function (): void { - parser.reset(); - testTerminal.clear(); - parser.currentState = 2; - parser.parse('\x7f'); - chai.expect(parser.currentState).equal(2); - 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 = 2; - parser.parse(collect[i]); - chai.expect(parser.currentState).equal(2); - chai.expect(parser.collected).equal(collect[i]); + }); + it('state ESCAPE_INTERMEDIATE collect action', function (): void { 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 = 2; - parser.parse(collect[i]); - chai.expect(parser.currentState).equal(0); - testTerminal.compare([['esc', '', collect[i]]]); + let collect = r(0x20, 0x30); + for (let i = 0; i < collect.length; ++i) { + parser.currentState = STATE.ESCAPE_INTERMEDIATE; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(STATE.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(); - } - }); - it('trans ANYWHERE/ESCAPE --> CSI_ENTRY with clear', function (): void { - parser.reset(); - // C0 - parser.currentState = 1; - parser.osc = '#'; - parser.params = [123]; - parser.collected = '#'; - parser.parse('['); - chai.expect(parser.currentState).equal(3); - chai.expect(parser.osc).equal(''); - chai.expect(parser.params).eql([0]); - chai.expect(parser.collected).equal(''); - parser.reset(); - // C1 - for (let state = 0; state < 14; ++state) { - parser.currentState = state; + let collect = r(0x30, 0x7f); + for (let i = 0; i < collect.length; ++i) { + parser.currentState = STATE.ESCAPE_INTERMEDIATE; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(STATE.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 = STATE.ESCAPE; parser.osc = '#'; parser.params = [123]; parser.collected = '#'; - parser.parse('\x9b'); - chai.expect(parser.currentState).equal(3); + parser.parse('['); + chai.expect(parser.currentState).equal(STATE.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 = 3; - parser.parse(exes[i]); - chai.expect(parser.currentState).equal(3); - testTerminal.compare([['exe', exes[i]]]); + // C1 + for (state in states) { + parser.currentState = state; + parser.osc = '#'; + parser.params = [123]; + parser.collected = '#'; + parser.parse('\x9b'); + chai.expect(parser.currentState).equal(STATE.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(); - } - }); - it('state CSI_ENTRY ignore', function (): void { - parser.reset(); - testTerminal.clear(); - parser.currentState = 3; - parser.parse('\x7f'); - chai.expect(parser.currentState).equal(3); - 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 = 3; - parser.parse(dispatches[i]); - chai.expect(parser.currentState).equal(0); - testTerminal.compare([['csi', '', [0], dispatches[i]]]); + let exes = r(0x00, 0x18); + exes.concat(['\x19']); + exes.concat(r(0x1c, 0x20)); + for (let i = 0; i < exes.length; ++i) { + parser.currentState = STATE.CSI_ENTRY; + parser.parse(exes[i]); + chai.expect(parser.currentState).equal(STATE.CSI_ENTRY); + testTerminal.compare([['exe', exes[i]]]); + parser.reset(); + testTerminal.clear(); + } + }); + it('state CSI_ENTRY ignore', function (): void { 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 = 3; - parser.parse(params[i]); - chai.expect(parser.currentState).equal(4); - chai.expect(parser.params).eql([params[i].charCodeAt(0) - 48]); + parser.currentState = STATE.CSI_ENTRY; + parser.parse('\x7f'); + chai.expect(parser.currentState).equal(STATE.CSI_ENTRY); + testTerminal.compare([]); parser.reset(); - } - // ';' - parser.currentState = 3; - parser.parse('\x3b'); - chai.expect(parser.currentState).equal(4); - chai.expect(parser.params).eql([0, 0]); - parser.reset(); - for (let i = 0; i < collect.length; ++i) { - parser.currentState = 3; - parser.parse(collect[i]); - chai.expect(parser.currentState).equal(4); - chai.expect(parser.collected).equal(collect[i]); + testTerminal.clear(); + }); + it('trans CSI_ENTRY --> GROUND with csi_dispatch action', function (): void { 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 = 4; - parser.parse(exes[i]); - chai.expect(parser.currentState).equal(4); - testTerminal.compare([['exe', exes[i]]]); + let dispatches = r(0x40, 0x7f); + for (let i = 0; i < dispatches.length; ++i) { + parser.currentState = STATE.CSI_ENTRY; + parser.parse(dispatches[i]); + chai.expect(parser.currentState).equal(STATE.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(); - 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 = 4; - parser.parse(params[i]); - chai.expect(parser.currentState).equal(4); - chai.expect(parser.params).eql([params[i].charCodeAt(0) - 48]); + 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 = STATE.CSI_ENTRY; + parser.parse(params[i]); + chai.expect(parser.currentState).equal(STATE.CSI_PARAM); + chai.expect(parser.params).eql([params[i].charCodeAt(0) - 48]); + parser.reset(); + } + parser.currentState = STATE.CSI_ENTRY; + parser.parse('\x3b'); + chai.expect(parser.currentState).equal(STATE.CSI_PARAM); + chai.expect(parser.params).eql([0, 0]); parser.reset(); - } - parser.currentState = 4; - parser.parse('\x3b'); - chai.expect(parser.currentState).equal(4); - chai.expect(parser.params).eql([0, 0]); - parser.reset(); - }); - it('state CSI_PARAM ignore', function (): void { - parser.reset(); - testTerminal.clear(); - parser.currentState = 4; - parser.parse('\x7f'); - chai.expect(parser.currentState).equal(4); - 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 = 4; - parser.params = [0, 1]; - parser.parse(dispatches[i]); - chai.expect(parser.currentState).equal(0); - testTerminal.compare([['csi', '', [0, 1], dispatches[i]]]); + for (let i = 0; i < collect.length; ++i) { + parser.currentState = STATE.CSI_ENTRY; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(STATE.CSI_PARAM); + chai.expect(parser.collected).equal(collect[i]); + parser.reset(); + } + }); + it('state CSI_PARAM execute rules', function (): void { 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 = 3; - parser.parse(collect[i]); - chai.expect(parser.currentState).equal(5); - chai.expect(parser.collected).equal(collect[i]); + let exes = r(0x00, 0x18); + exes.concat(['\x19']); + exes.concat(r(0x1c, 0x20)); + for (let i = 0; i < exes.length; ++i) { + parser.currentState = STATE.CSI_PARAM; + parser.parse(exes[i]); + chai.expect(parser.currentState).equal(STATE.CSI_PARAM); + testTerminal.compare([['exe', exes[i]]]); + parser.reset(); + testTerminal.clear(); + } + }); + it('state CSI_PARAM param action', function (): void { 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 = 4; - parser.parse(collect[i]); - chai.expect(parser.currentState).equal(5); - chai.expect(parser.collected).equal(collect[i]); + let params = ['\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', '\x38', '\x39']; + for (let i = 0; i < params.length; ++i) { + parser.currentState = STATE.CSI_PARAM; + parser.parse(params[i]); + chai.expect(parser.currentState).equal(STATE.CSI_PARAM); + chai.expect(parser.params).eql([params[i].charCodeAt(0) - 48]); + parser.reset(); + } + parser.currentState = STATE.CSI_PARAM; + parser.parse('\x3b'); + chai.expect(parser.currentState).equal(STATE.CSI_PARAM); + chai.expect(parser.params).eql([0, 0]); 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 = 5; - parser.parse(exes[i]); - chai.expect(parser.currentState).equal(5); - testTerminal.compare([['exe', exes[i]]]); + }); + it('state CSI_PARAM ignore', function (): void { 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 = 5; - parser.parse(collect[i]); - chai.expect(parser.currentState).equal(5); - chai.expect(parser.collected).equal(collect[i]); - parser.reset(); - } - }); - it('state CSI_INTERMEDIATE ignore', function (): void { - parser.reset(); - testTerminal.clear(); - parser.currentState = 5; - parser.parse('\x7f'); - chai.expect(parser.currentState).equal(5); - 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 = 5; - parser.params = [0, 1]; - parser.parse(dispatches[i]); - chai.expect(parser.currentState).equal(0); - testTerminal.compare([['csi', '', [0, 1], dispatches[i]]]); + parser.currentState = STATE.CSI_PARAM; + parser.parse('\x7f'); + chai.expect(parser.currentState).equal(STATE.CSI_PARAM); + testTerminal.compare([]); parser.reset(); testTerminal.clear(); - } - }); - it('trans CSI_ENTRY --> CSI_IGNORE', function (): void { - parser.reset(); - parser.currentState = 3; - parser.parse('\x3a'); - chai.expect(parser.currentState).equal(6); - 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 = 4; - parser.parse('\x3b' + chars[i]); - chai.expect(parser.currentState).equal(6); - chai.expect(parser.params).eql([0, 0]); + }); + it('trans CSI_PARAM --> GROUND with csi_dispatch action', function (): void { 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 = 5; - parser.parse(chars[i]); - chai.expect(parser.currentState).equal(6); - chai.expect(parser.params).eql([0]); + let dispatches = r(0x40, 0x7f); + for (let i = 0; i < dispatches.length; ++i) { + parser.currentState = STATE.CSI_PARAM; + parser.params = [0, 1]; + parser.parse(dispatches[i]); + chai.expect(parser.currentState).equal(STATE.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(); - } - }); - 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 = 6; - parser.parse(exes[i]); - chai.expect(parser.currentState).equal(6); - testTerminal.compare([['exe', exes[i]]]); + let collect = r(0x20, 0x30); + for (let i = 0; i < collect.length; ++i) { + parser.currentState = STATE.CSI_ENTRY; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(STATE.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 = STATE.CSI_PARAM; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(STATE.CSI_INTERMEDIATE); + chai.expect(parser.collected).equal(collect[i]); + parser.reset(); + } + }); + it('state CSI_INTERMEDIATE execute rules', function (): void { 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 = 6; - parser.parse(ignored[i]); - chai.expect(parser.currentState).equal(6); - testTerminal.compare([]); + let exes = r(0x00, 0x18); + exes.concat(['\x19']); + exes.concat(r(0x1c, 0x20)); + for (let i = 0; i < exes.length; ++i) { + parser.currentState = STATE.CSI_INTERMEDIATE; + parser.parse(exes[i]); + chai.expect(parser.currentState).equal(STATE.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 = STATE.CSI_INTERMEDIATE; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(STATE.CSI_INTERMEDIATE); + chai.expect(parser.collected).equal(collect[i]); + parser.reset(); + } + }); + it('state CSI_INTERMEDIATE ignore', function (): void { 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 = 6; - parser.params = [0, 1]; - parser.parse(dispatches[i]); - chai.expect(parser.currentState).equal(0); + parser.currentState = STATE.CSI_INTERMEDIATE; + parser.parse('\x7f'); + chai.expect(parser.currentState).equal(STATE.CSI_INTERMEDIATE); 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(7); + }); + it('trans CSI_INTERMEDIATE --> GROUND with csi_dispatch action', function (): void { parser.reset(); - } - // C1 - for (let state = 0; state < 14; ++state) { - parser.currentState = state; - initializers = ['\x98', '\x9e', '\x9f']; + let dispatches = r(0x40, 0x7f); + for (let i = 0; i < dispatches.length; ++i) { + parser.currentState = STATE.CSI_INTERMEDIATE; + parser.params = [0, 1]; + parser.parse(dispatches[i]); + chai.expect(parser.currentState).equal(STATE.GROUND); + testTerminal.compare([['csi', '', [0, 1], dispatches[i]]]); + parser.reset(); + testTerminal.clear(); + } + }); + it('trans CSI_ENTRY --> CSI_IGNORE', function (): void { + parser.reset(); + parser.currentState = STATE.CSI_ENTRY; + parser.parse('\x3a'); + chai.expect(parser.currentState).equal(STATE.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 = STATE.CSI_PARAM; + parser.parse('\x3b' + chars[i]); + chai.expect(parser.currentState).equal(STATE.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 = STATE.CSI_INTERMEDIATE; + parser.parse(chars[i]); + chai.expect(parser.currentState).equal(STATE.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 = STATE.CSI_IGNORE; + parser.parse(exes[i]); + chai.expect(parser.currentState).equal(STATE.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 = STATE.CSI_IGNORE; + parser.parse(ignored[i]); + chai.expect(parser.currentState).equal(STATE.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 = STATE.CSI_IGNORE; + parser.params = [0, 1]; + parser.parse(dispatches[i]); + chai.expect(parser.currentState).equal(STATE.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(initializers[i]); - chai.expect(parser.currentState).equal(7); + parser.parse('\x1b' + initializers[i]); + chai.expect(parser.currentState).equal(STATE.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 = 7; - parser.parse(ignored[i]); - chai.expect(parser.currentState).equal(7); + // 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(STATE.SOS_PM_APC_STRING); + parser.reset(); + } + } + }); + it('state SOS_PM_APC_STRING ignore rules', function (): void { parser.reset(); - } - }); - it('trans ANYWHERE/ESCAPE --> OSC_STRING', function (): void { - parser.reset(); - // C0 - parser.parse('\x1b]'); - chai.expect(parser.currentState).equal(8); - parser.reset(); - // C1 - for (let state = 0; state < 14; ++state) { - parser.currentState = state; - parser.parse('\x9d'); - chai.expect(parser.currentState).equal(8); + 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 = STATE.SOS_PM_APC_STRING; + parser.parse(ignored[i]); + chai.expect(parser.currentState).equal(STATE.SOS_PM_APC_STRING); + parser.reset(); + } + }); + it('trans ANYWHERE/ESCAPE --> OSC_STRING', function (): void { 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 = 8; - parser.parse(ignored[i]); - chai.expect(parser.currentState).equal(8); - chai.expect(parser.osc).equal(''); + // C0 + parser.parse('\x1b]'); + chai.expect(parser.currentState).equal(STATE.OSC_STRING); 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 = 8; - parser.parse(puts[i]); - chai.expect(parser.currentState).equal(8); - chai.expect(parser.osc).equal(puts[i]); + // C1 + for (state in states) { + parser.currentState = state; + parser.parse('\x9d'); + chai.expect(parser.currentState).equal(STATE.OSC_STRING); + parser.reset(); + } + }); + it('state OSC_STRING ignore rules', function (): void { parser.reset(); - } - }); - it('state DCS_ENTRY', function (): void { - parser.reset(); - // C0 - parser.parse('\x1bP'); - chai.expect(parser.currentState).equal(9); - parser.reset(); - // C1 - for (let state = 0; state < 14; ++state) { - parser.currentState = state; - parser.parse('\x90'); - chai.expect(parser.currentState).equal(9); + 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 = STATE.OSC_STRING; + parser.parse(ignored[i]); + chai.expect(parser.currentState).equal(STATE.OSC_STRING); + chai.expect(parser.osc).equal(''); + parser.reset(); + } + }); + it('state OSC_STRING put action', function (): void { 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 = 9; - parser.parse(ignored[i]); - chai.expect(parser.currentState).equal(9); + let puts = r(0x20, 0x80); + for (let i = 0; i < puts.length; ++i) { + parser.currentState = STATE.OSC_STRING; + parser.parse(puts[i]); + chai.expect(parser.currentState).equal(STATE.OSC_STRING); + chai.expect(parser.osc).equal(puts[i]); + parser.reset(); + } + }); + it('state DCS_ENTRY', function (): void { 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 = 9; - parser.parse(params[i]); - chai.expect(parser.currentState).equal(10); - chai.expect(parser.params).eql([params[i].charCodeAt(0) - 48]); + // C0 + parser.parse('\x1bP'); + chai.expect(parser.currentState).equal(STATE.DCS_ENTRY); parser.reset(); - } - parser.currentState = 9; - parser.parse('\x3b'); - chai.expect(parser.currentState).equal(10); - chai.expect(parser.params).eql([0, 0]); - parser.reset(); - for (let i = 0; i < collect.length; ++i) { - parser.currentState = 9; - parser.parse(collect[i]); - chai.expect(parser.currentState).equal(10); - chai.expect(parser.collected).equal(collect[i]); + // C1 + for (state in states) { + parser.currentState = state; + parser.parse('\x90'); + chai.expect(parser.currentState).equal(STATE.DCS_ENTRY); + parser.reset(); + } + }); + it('state DCS_ENTRY ignore rules', function (): void { 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 = 10; - parser.parse(ignored[i]); - chai.expect(parser.currentState).equal(10); + 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 = STATE.DCS_ENTRY; + parser.parse(ignored[i]); + chai.expect(parser.currentState).equal(STATE.DCS_ENTRY); + parser.reset(); + } + }); + it('state DCS_ENTRY --> DCS_PARAM with param/collect actions', function (): void { 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 = 10; - parser.parse(params[i]); - chai.expect(parser.currentState).equal(10); - chai.expect(parser.params).eql([params[i].charCodeAt(0) - 48]); + 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 = STATE.DCS_ENTRY; + parser.parse(params[i]); + chai.expect(parser.currentState).equal(STATE.DCS_PARAM); + chai.expect(parser.params).eql([params[i].charCodeAt(0) - 48]); + parser.reset(); + } + parser.currentState = STATE.DCS_ENTRY; + parser.parse('\x3b'); + chai.expect(parser.currentState).equal(STATE.DCS_PARAM); + chai.expect(parser.params).eql([0, 0]); parser.reset(); - } - parser.currentState = 10; - parser.parse('\x3b'); - chai.expect(parser.currentState).equal(10); - chai.expect(parser.params).eql([0, 0]); - parser.reset(); - }); - it('trans DCS_ENTRY --> DCS_IGNORE', function (): void { - parser.reset(); - parser.currentState = 9; - parser.parse('\x3a'); - chai.expect(parser.currentState).equal(11); - 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 = 10; - parser.parse('\x3b' + chars[i]); - chai.expect(parser.currentState).equal(11); + for (let i = 0; i < collect.length; ++i) { + parser.currentState = STATE.DCS_ENTRY; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(STATE.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 = STATE.DCS_PARAM; + parser.parse(ignored[i]); + chai.expect(parser.currentState).equal(STATE.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 = STATE.DCS_PARAM; + parser.parse(params[i]); + chai.expect(parser.currentState).equal(STATE.DCS_PARAM); + chai.expect(parser.params).eql([params[i].charCodeAt(0) - 48]); + parser.reset(); + } + parser.currentState = STATE.DCS_PARAM; + parser.parse('\x3b'); + chai.expect(parser.currentState).equal(STATE.DCS_PARAM); 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 = 12; - parser.parse(chars[i]); - chai.expect(parser.currentState).equal(11); + }); + it('trans DCS_ENTRY --> DCS_IGNORE', function (): void { 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 = 11; - parser.parse(ignored[i]); - chai.expect(parser.currentState).equal(11); + parser.currentState = STATE.DCS_ENTRY; + parser.parse('\x3a'); + chai.expect(parser.currentState).equal(STATE.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 = 9; - parser.parse(collect[i]); - chai.expect(parser.currentState).equal(12); - chai.expect(parser.collected).equal(collect[i]); + }); + it('trans DCS_PARAM --> DCS_IGNORE', function (): void { 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 = 10; - parser.parse(collect[i]); - chai.expect(parser.currentState).equal(12); - chai.expect(parser.collected).equal(collect[i]); + let chars = ['\x3a', '\x3c', '\x3d', '\x3e', '\x3f']; + for (let i = 0; i < chars.length; ++i) { + parser.currentState = STATE.DCS_PARAM; + parser.parse('\x3b' + chars[i]); + chai.expect(parser.currentState).equal(STATE.DCS_IGNORE); + chai.expect(parser.params).eql([0, 0]); + parser.reset(); + } + }); + it('trans DCS_INTERMEDIATE --> DCS_IGNORE', function (): void { 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 = 12; - parser.parse(ignored[i]); - chai.expect(parser.currentState).equal(12); + let chars = r(0x30, 0x40); + for (let i = 0; i < chars.length; ++i) { + parser.currentState = STATE.DCS_INTERMEDIATE; + parser.parse(chars[i]); + chai.expect(parser.currentState).equal(STATE.DCS_IGNORE); + parser.reset(); + } + }); + it('state DCS_IGNORE ignore rules', function (): void { 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 = 12; - parser.parse(collect[i]); - chai.expect(parser.currentState).equal(12); - chai.expect(parser.collected).equal(collect[i]); + 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 = STATE.DCS_IGNORE; + parser.parse(ignored[i]); + chai.expect(parser.currentState).equal(STATE.DCS_IGNORE); + parser.reset(); + } + }); + it('trans DCS_ENTRY --> DCS_INTERMEDIATE with collect action', function (): void { 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 = 12; - parser.parse('\x20' + chars[i]); - chai.expect(parser.currentState).equal(11); - chai.expect(parser.collected).equal('\x20'); + let collect = r(0x20, 0x30); + for (let i = 0; i < collect.length; ++i) { + parser.currentState = STATE.DCS_ENTRY; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(STATE.DCS_INTERMEDIATE); + chai.expect(parser.collected).equal(collect[i]); + parser.reset(); + } + }); + it('trans DCS_PARAM --> DCS_INTERMEDIATE with collect action', function (): void { 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 = 9; - parser.parse(collect[i]); - chai.expect(parser.currentState).equal(13); - testTerminal.compare([['dcs hook', '', [0], collect[i]]]); + let collect = r(0x20, 0x30); + for (let i = 0; i < collect.length; ++i) { + parser.currentState = STATE.DCS_PARAM; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(STATE.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 = STATE.DCS_INTERMEDIATE; + parser.parse(ignored[i]); + chai.expect(parser.currentState).equal(STATE.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 = STATE.DCS_INTERMEDIATE; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(STATE.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 = STATE.DCS_INTERMEDIATE; + parser.parse('\x20' + chars[i]); + chai.expect(parser.currentState).equal(STATE.DCS_IGNORE); + chai.expect(parser.collected).equal('\x20'); + parser.reset(); + } + }); + it('trans DCS_ENTRY --> DCS_PASSTHROUGH with hook', function (): void { 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 = 10; - parser.parse(collect[i]); - chai.expect(parser.currentState).equal(13); - testTerminal.compare([['dcs hook', '', [0], collect[i]]]); + let collect = r(0x40, 0x7f); + for (let i = 0; i < collect.length; ++i) { + parser.currentState = STATE.DCS_ENTRY; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(STATE.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(); - } - }); - 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 = 12; - parser.parse(collect[i]); - chai.expect(parser.currentState).equal(13); - testTerminal.compare([['dcs hook', '', [0], collect[i]]]); + let collect = r(0x40, 0x7f); + for (let i = 0; i < collect.length; ++i) { + parser.currentState = STATE.DCS_PARAM; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(STATE.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(); - } - }); - 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 = 13; - parser.parse(puts[i]); - chai.expect(parser.currentState).equal(13); - testTerminal.compare([['dcs put', puts[i]]]); + let collect = r(0x40, 0x7f); + for (let i = 0; i < collect.length; ++i) { + parser.currentState = STATE.DCS_INTERMEDIATE; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(STATE.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(); - } - }); - it('state DCS_PASSTHROUGH ignore', function (): void { - parser.reset(); - testTerminal.clear(); - parser.currentState = 13; - parser.parse('\x7f'); - chai.expect(parser.currentState).equal(13); - testTerminal.compare([]); - 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 = STATE.DCS_PASSTHROUGH; + parser.parse(puts[i]); + chai.expect(parser.currentState).equal(STATE.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 = STATE.DCS_PASSTHROUGH; + parser.parse('\x7f'); + chai.expect(parser.currentState).equal(STATE.DCS_PASSTHROUGH); + testTerminal.compare([]); + parser.reset(); + testTerminal.clear(); + }); }); -}); -function test(s: string, value: any, noReset: any): void { - if (!noReset) { - 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); } - 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'] + 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); + }); }); - 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 = 6; - parser.parse('€öäü'); - chai.expect(parser.currentState).equal(6); - testTerminal.compare([]); - parser.reset(); - testTerminal.clear(); - }); - it('DCS_IGNORE error', function (): void { - parser.reset(); - testTerminal.clear(); - parser.currentState = 11; - parser.parse('€öäü'); - chai.expect(parser.currentState).equal(11); - testTerminal.compare([]); - parser.reset(); - testTerminal.clear(); - }); - it('DCS_PASSTHROUGH error', function (): void { - parser.reset(); - testTerminal.clear(); - parser.currentState = 13; - parser.parse('€öäü'); - chai.expect(parser.currentState).equal(13); - testTerminal.compare([['dcs put', '€öäü']]); - parser.reset(); - testTerminal.clear(); - }); - it('error else of if (code > 159)', function (): void { - parser.reset(); - testTerminal.clear(); - parser.currentState = 0; - parser.parse('\x1e'); - chai.expect(parser.currentState).equal(0); - testTerminal.compare([]); - parser.reset(); - testTerminal.clear(); + describe('coverage tests', function(): void { + it('CSI_IGNORE error', function (): void { + parser.reset(); + testTerminal.clear(); + parser.currentState = STATE.CSI_IGNORE; + parser.parse('€öäü'); + chai.expect(parser.currentState).equal(STATE.CSI_IGNORE); + testTerminal.compare([]); + parser.reset(); + testTerminal.clear(); + }); + it('DCS_IGNORE error', function (): void { + parser.reset(); + testTerminal.clear(); + parser.currentState = STATE.DCS_IGNORE; + parser.parse('€öäü'); + chai.expect(parser.currentState).equal(STATE.DCS_IGNORE); + testTerminal.compare([]); + parser.reset(); + testTerminal.clear(); + }); + it('DCS_PASSTHROUGH error', function (): void { + parser.reset(); + testTerminal.clear(); + parser.currentState = STATE.DCS_PASSTHROUGH; + parser.parse('€öäü'); + chai.expect(parser.currentState).equal(STATE.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 = STATE.GROUND; + parser.parse('\x1e'); + chai.expect(parser.currentState).equal(STATE.GROUND); + testTerminal.compare([]); + parser.reset(); + testTerminal.clear(); + }); }); -}); -let errorTerminal1 = function(): void {}; -errorTerminal1.prototype = testTerminal; -let errTerminal1 = new errorTerminal1(); -errTerminal1.inst_E = function(e: any): void { - this.calls.push(['error', e]); -}; -let errParser1 = new AnsiParser(errTerminal1); + 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); -let errorTerminal2 = function(): void {}; -errorTerminal2.prototype = testTerminal; -let errTerminal2 = new errorTerminal2(); -errTerminal2.inst_E = function(e: any): any { - this.calls.push(['error', e]); - return true; // --> abort parsing -}; -let errParser2 = new AnsiParser(errTerminal2); + 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); -describe('error tests', function(): void { - it('CSI_PARAM unicode error - inst_E output w/o abort', function (): void { - errParser1.parse('\x1b[<31;5€normal print'); - errTerminal1.compare([ - ['error', { - pos: 7, - character: '€', - state: 4, - print: -1, - dcs: -1, - osc: '', - collect: '<', - params: [31, 5]}], - ['print', 'normal print'] - ]); - parser.reset(); - testTerminal.clear(); - }); - it('CSI_PARAM unicode error - inst_E output with abort', function (): void { - errParser2.parse('\x1b[<31;5€no print'); - errTerminal2.compare([ - ['error', { - pos: 7, - character: '€', - state: 4, - print: -1, - dcs: -1, - osc: '', - collect: '<', - params: [31, 5]}] - ]); - parser.reset(); - testTerminal.clear(); + 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', { + pos: 7, + code: '€'.charCodeAt(0), + state: 4, + print: -1, + dcs: -1, + osc: '', + collect: '<', + 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', { + pos: 7, + code: '€'.charCodeAt(0), + state: 4, + print: -1, + dcs: -1, + osc: '', + collect: '<', + params: [31, 5]}] + ]); + parser.reset(); + testTerminal.clear(); + }); }); + }); diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts index 6facddeab8..072ab882d8 100644 --- a/src/EscapeSequenceParser.ts +++ b/src/EscapeSequenceParser.ts @@ -1,17 +1,62 @@ +import { IInputHandler, IInputHandlingTerminal } from './Types'; +import { CHARSETS, DEFAULT_CHARSET } from './Charsets'; +import { C0 } from './EscapeSequences'; + + +// terminal interface for the escape sequence parser export interface IParserTerminal { - inst_p?: (s: string, start: number, end: number) => void; - inst_o?: (s: string) => void; - inst_x?: (flag: string) => void; - inst_c?: (collected: string, params: number[], flag: string) => void; - inst_e?: (collected: string, flag: string) => void; - inst_H?: (collected: string, params: number[], flag: string) => void; - inst_P?: (dcs: string) => void; - inst_U?: () => void; - inst_E?: () => void; // TODO: real signature + actionPrint?: (data: string, start: number, end: number) => void; + actionOSC?: (data: string) => void; + actionExecute?: (flag: string) => void; + actionCSI?: (collected: string, params: number[], flag: string) => void; + actionESC?: (collected: string, flag: string) => void; + actionDCSHook?: (collected: string, params: number[], flag: string) => void; + actionDCSPrint?: (data: string, start: number, end: number) => void; + actionDCSUnhook?: () => void; + actionError?: () => void; // FIXME: real signature and error handling +} + + +// FSM states +export const enum STATE { + GROUND = 0, + ESCAPE, + ESCAPE_INTERMEDIATE, + CSI_ENTRY, + CSI_PARAM, + CSI_INTERMEDIATE, + CSI_IGNORE, + SOS_PM_APC_STRING, + OSC_STRING, + DCS_ENTRY, + DCS_PARAM, + DCS_IGNORE, + DCS_INTERMEDIATE, + DCS_PASSTHROUGH +} + +// FSM actions +export const enum ACTION { + ignore = 0, + error, + print, + execute, + osc_start, + osc_put, + osc_end, + csi_dispatch, + param, + collect, + esc_dispatch, + clear, + dcs_hook, + dcs_put, + dcs_unhook } -export function r(a: number, b: number): number[] { +// number range macro +function r(a: number, b: number): number[] { let c = b - a; let arr = new Array(c); while (c--) { @@ -21,15 +66,20 @@ export function r(a: number, b: number): number[] { } +// transition table of the FSM +// TODO: fallback to array export class TransitionTable { public table: Uint8Array; + constructor(length: number) { this.table = new Uint8Array(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_list(inps: number[], state: number, action: number | null, next: number | null): void { + + 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); } @@ -37,125 +87,139 @@ export class TransitionTable { } +// default definitions of printable and executable characters let PRINTABLES = r(0x20, 0x7f); let EXECUTABLES = r(0x00, 0x18); EXECUTABLES.push(0x19); EXECUTABLES.concat(r(0x1c, 0x20)); +// default transition of the FSM is [error, GROUND] +let DEFAULT_TRANSITION = ACTION.error << 4 | STATE.GROUND; + +// default DEC/ANSI compatible state transition table +// as defined by https://vt100.net/emu/dec_ansi_parser +export const VT500_TRANSITION_TABLE = (function (): TransitionTable { + let table: TransitionTable = new TransitionTable(4095); -export const TRANSITION_TABLE = (function (): TransitionTable { - let t: TransitionTable = new TransitionTable(4095); + let states: number[] = r(STATE.GROUND, STATE.DCS_PASSTHROUGH + 1); + let state: any; // table with default transition [any] --> [error, GROUND] - for (let state = 0; state < 14; ++state) { + 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) { - t[state << 8 | code] = 16; + table[state << 8 | code] = DEFAULT_TRANSITION; } } // apply transitions // printables - t.add_list(PRINTABLES, 0, 2, 0); + table.addMany(PRINTABLES, STATE.GROUND, ACTION.print, STATE.GROUND); // global anywhere rules - for (let state = 0; state < 14; ++state) { - t.add_list([0x18, 0x1a, 0x99, 0x9a], state, 3, 0); - t.add_list(r(0x80, 0x90), state, 3, 0); - t.add_list(r(0x90, 0x98), state, 3, 0); - t.add(0x9c, state, 0, 0); // ST as terminator - t.add(0x1b, state, 11, 1); // ESC - t.add(0x9d, state, 4, 8); // OSC - t.add_list([0x98, 0x9e, 0x9f], state, 0, 7); - t.add(0x9b, state, 11, 3); // CSI - t.add(0x90, state, 11, 9); // DCS + for (state in states) { + table.addMany([0x18, 0x1a, 0x99, 0x9a], state, ACTION.execute, STATE.GROUND); + table.addMany(r(0x80, 0x90), state, ACTION.execute, STATE.GROUND); + table.addMany(r(0x90, 0x98), state, ACTION.execute, STATE.GROUND); + table.add(0x9c, state, ACTION.ignore, STATE.GROUND); // ST as terminator + table.add(0x1b, state, ACTION.clear, STATE.ESCAPE); // ESC + table.add(0x9d, state, ACTION.osc_start, STATE.OSC_STRING); // OSC + table.addMany([0x98, 0x9e, 0x9f], state, ACTION.ignore, STATE.SOS_PM_APC_STRING); + table.add(0x9b, state, ACTION.clear, STATE.CSI_ENTRY); // CSI + table.add(0x90, state, ACTION.clear, STATE.DCS_ENTRY); // DCS } // rules for executables and 7f - t.add_list(EXECUTABLES, 0, 3, 0); - t.add_list(EXECUTABLES, 1, 3, 1); - t.add(0x7f, 1, null, 1); - t.add_list(EXECUTABLES, 8, null, 8); - t.add_list(EXECUTABLES, 3, 3, 3); - t.add(0x7f, 3, null, 3); - t.add_list(EXECUTABLES, 4, 3, 4); - t.add(0x7f, 4, null, 4); - t.add_list(EXECUTABLES, 6, 3, 6); - t.add_list(EXECUTABLES, 5, 3, 5); - t.add(0x7f, 5, null, 5); - t.add_list(EXECUTABLES, 2, 3, 2); - t.add(0x7f, 2, null, 2); + table.addMany(EXECUTABLES, STATE.GROUND, ACTION.execute, STATE.GROUND); + table.addMany(EXECUTABLES, STATE.ESCAPE, ACTION.execute, STATE.ESCAPE); + table.add(0x7f, STATE.ESCAPE, ACTION.ignore, STATE.ESCAPE); + table.addMany(EXECUTABLES, STATE.OSC_STRING, ACTION.ignore, STATE.OSC_STRING); + table.addMany(EXECUTABLES, STATE.CSI_ENTRY, ACTION.execute, STATE.CSI_ENTRY); + table.add(0x7f, STATE.CSI_ENTRY, ACTION.ignore, STATE.CSI_ENTRY); + table.addMany(EXECUTABLES, STATE.CSI_PARAM, ACTION.execute, STATE.CSI_PARAM); + table.add(0x7f, STATE.CSI_PARAM, ACTION.ignore, STATE.CSI_PARAM); + table.addMany(EXECUTABLES, STATE.CSI_IGNORE, ACTION.execute, STATE.CSI_IGNORE); + table.addMany(EXECUTABLES, STATE.CSI_INTERMEDIATE, ACTION.execute, STATE.CSI_INTERMEDIATE); + table.add(0x7f, STATE.CSI_INTERMEDIATE, ACTION.ignore, STATE.CSI_INTERMEDIATE); + table.addMany(EXECUTABLES, STATE.ESCAPE_INTERMEDIATE, ACTION.execute, STATE.ESCAPE_INTERMEDIATE); + table.add(0x7f, STATE.ESCAPE_INTERMEDIATE, ACTION.ignore, STATE.ESCAPE_INTERMEDIATE); // osc - t.add(0x5d, 1, 4, 8); - t.add_list(PRINTABLES, 8, 5, 8); - t.add(0x7f, 8, 5, 8); - t.add_list([0x9c, 0x1b, 0x18, 0x1a, 0x07], 8, 6, 0); - t.add_list(r(0x1c, 0x20), 8, 0, 8); + table.add(0x5d, STATE.ESCAPE, ACTION.osc_start, STATE.OSC_STRING); + table.addMany(PRINTABLES, STATE.OSC_STRING, ACTION.osc_put, STATE.OSC_STRING); + table.add(0x7f, STATE.OSC_STRING, ACTION.osc_put, STATE.OSC_STRING); + table.addMany([0x9c, 0x1b, 0x18, 0x1a, 0x07], STATE.OSC_STRING, ACTION.osc_end, STATE.GROUND); + table.addMany(r(0x1c, 0x20), STATE.OSC_STRING, ACTION.ignore, STATE.OSC_STRING); // sos/pm/apc does nothing - t.add_list([0x58, 0x5e, 0x5f], 1, 0, 7); - t.add_list(PRINTABLES, 7, null, 7); - t.add_list(EXECUTABLES, 7, null, 7); - t.add(0x9c, 7, 0, 0); + table.addMany([0x58, 0x5e, 0x5f], STATE.ESCAPE, ACTION.ignore, STATE.SOS_PM_APC_STRING); + table.addMany(PRINTABLES, STATE.SOS_PM_APC_STRING, ACTION.ignore, STATE.SOS_PM_APC_STRING); + table.addMany(EXECUTABLES, STATE.SOS_PM_APC_STRING, ACTION.ignore, STATE.SOS_PM_APC_STRING); + table.add(0x9c, STATE.SOS_PM_APC_STRING, ACTION.ignore, STATE.GROUND); // csi entries - t.add(0x5b, 1, 11, 3); - t.add_list(r(0x40, 0x7f), 3, 7, 0); - t.add_list(r(0x30, 0x3a), 3, 8, 4); - t.add(0x3b, 3, 8, 4); - t.add_list([0x3c, 0x3d, 0x3e, 0x3f], 3, 9, 4); - t.add_list(r(0x30, 0x3a), 4, 8, 4); - t.add(0x3b, 4, 8, 4); - t.add_list(r(0x40, 0x7f), 4, 7, 0); - t.add_list([0x3a, 0x3c, 0x3d, 0x3e, 0x3f], 4, 0, 6); - t.add_list(r(0x20, 0x40), 6, null, 6); - t.add(0x7f, 6, null, 6); - t.add_list(r(0x40, 0x7f), 6, 0, 0); - t.add(0x3a, 3, 0, 6); - t.add_list(r(0x20, 0x30), 3, 9, 5); - t.add_list(r(0x20, 0x30), 5, 9, 5); - t.add_list(r(0x30, 0x40), 5, 0, 6); - t.add_list(r(0x40, 0x7f), 5, 7, 0); - t.add_list(r(0x20, 0x30), 4, 9, 5); + table.add(0x5b, STATE.ESCAPE, ACTION.clear, STATE.CSI_ENTRY); + table.addMany(r(0x40, 0x7f), STATE.CSI_ENTRY, ACTION.csi_dispatch, STATE.GROUND); + table.addMany(r(0x30, 0x3a), STATE.CSI_ENTRY, ACTION.param, STATE.CSI_PARAM); + table.add(0x3b, STATE.CSI_ENTRY, ACTION.param, STATE.CSI_PARAM); + table.addMany([0x3c, 0x3d, 0x3e, 0x3f], STATE.CSI_ENTRY, ACTION.collect, STATE.CSI_PARAM); + table.addMany(r(0x30, 0x3a), STATE.CSI_PARAM, ACTION.param, STATE.CSI_PARAM); + table.add(0x3b, STATE.CSI_PARAM, ACTION.param, STATE.CSI_PARAM); + table.addMany(r(0x40, 0x7f), STATE.CSI_PARAM, ACTION.csi_dispatch, STATE.GROUND); + table.addMany([0x3a, 0x3c, 0x3d, 0x3e, 0x3f], STATE.CSI_PARAM, ACTION.ignore, STATE.CSI_IGNORE); + table.addMany(r(0x20, 0x40), STATE.CSI_IGNORE, null, STATE.CSI_IGNORE); + table.add(0x7f, STATE.CSI_IGNORE, null, STATE.CSI_IGNORE); + table.addMany(r(0x40, 0x7f), STATE.CSI_IGNORE, ACTION.ignore, STATE.GROUND); + table.add(0x3a, STATE.CSI_ENTRY, ACTION.ignore, STATE.CSI_IGNORE); + table.addMany(r(0x20, 0x30), STATE.CSI_ENTRY, ACTION.collect, STATE.CSI_INTERMEDIATE); + table.addMany(r(0x20, 0x30), STATE.CSI_INTERMEDIATE, ACTION.collect, STATE.CSI_INTERMEDIATE); + table.addMany(r(0x30, 0x40), STATE.CSI_INTERMEDIATE, ACTION.ignore, STATE.CSI_IGNORE); + table.addMany(r(0x40, 0x7f), STATE.CSI_INTERMEDIATE, ACTION.csi_dispatch, STATE.GROUND); + table.addMany(r(0x20, 0x30), STATE.CSI_PARAM, ACTION.collect, STATE.CSI_INTERMEDIATE); // esc_intermediate - t.add_list(r(0x20, 0x30), 1, 9, 2); - t.add_list(r(0x20, 0x30), 2, 9, 2); - t.add_list(r(0x30, 0x7f), 2, 10, 0); - t.add_list(r(0x30, 0x50), 1, 10, 0); - t.add_list([0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x59, 0x5a, 0x5c], 1, 10, 0); - t.add_list(r(0x60, 0x7f), 1, 10, 0); + table.addMany(r(0x20, 0x30), STATE.ESCAPE, ACTION.collect, STATE.ESCAPE_INTERMEDIATE); + table.addMany(r(0x20, 0x30), STATE.ESCAPE_INTERMEDIATE, ACTION.collect, STATE.ESCAPE_INTERMEDIATE); + table.addMany(r(0x30, 0x7f), STATE.ESCAPE_INTERMEDIATE, ACTION.esc_dispatch, STATE.GROUND); + table.addMany(r(0x30, 0x50), STATE.ESCAPE, ACTION.esc_dispatch, STATE.GROUND); + table.addMany(r(0x51, 0x58), STATE.ESCAPE, ACTION.esc_dispatch, STATE.GROUND); + table.addMany([0x59, 0x5a, 0x5c], STATE.ESCAPE, ACTION.esc_dispatch, STATE.GROUND); + table.addMany(r(0x60, 0x7f), STATE.ESCAPE, ACTION.esc_dispatch, STATE.GROUND); // dcs entry - t.add(0x50, 1, 11, 9); - t.add_list(EXECUTABLES, 9, null, 9); - t.add(0x7f, 9, null, 9); - t.add_list(r(0x1c, 0x20), 9, null, 9); - t.add_list(r(0x20, 0x30), 9, 9, 12); - t.add(0x3a, 9, 0, 11); - t.add_list(r(0x30, 0x3a), 9, 8, 10); - t.add(0x3b, 9, 8, 10); - t.add_list([0x3c, 0x3d, 0x3e, 0x3f], 9, 9, 10); - t.add_list(EXECUTABLES, 11, null, 11); - t.add_list(r(0x20, 0x80), 11, null, 11); - t.add_list(r(0x1c, 0x20), 11, null, 11); - t.add_list(EXECUTABLES, 10, null, 10); - t.add(0x7f, 10, null, 10); - t.add_list(r(0x1c, 0x20), 10, null, 10); - t.add_list(r(0x30, 0x3a), 10, 8, 10); - t.add(0x3b, 10, 8, 10); - t.add_list([0x3a, 0x3c, 0x3d, 0x3e, 0x3f], 10, 0, 11); - t.add_list(r(0x20, 0x30), 10, 9, 12); - t.add_list(EXECUTABLES, 12, null, 12); - t.add(0x7f, 12, null, 12); - t.add_list(r(0x1c, 0x20), 12, null, 12); - t.add_list(r(0x20, 0x30), 12, 9, 12); - t.add_list(r(0x30, 0x40), 12, 0, 11); - t.add_list(r(0x40, 0x7f), 12, 12, 13); - t.add_list(r(0x40, 0x7f), 10, 12, 13); - t.add_list(r(0x40, 0x7f), 9, 12, 13); - t.add_list(EXECUTABLES, 13, 13, 13); - t.add_list(PRINTABLES, 13, 13, 13); - t.add(0x7f, 13, null, 13); - t.add_list([0x1b, 0x9c], 13, 14, 0); - - return t; + table.add(0x50, STATE.ESCAPE, ACTION.clear, STATE.DCS_ENTRY); + table.addMany(EXECUTABLES, STATE.DCS_ENTRY, ACTION.ignore, STATE.DCS_ENTRY); + table.add(0x7f, STATE.DCS_ENTRY, ACTION.ignore, STATE.DCS_ENTRY); + table.addMany(r(0x1c, 0x20), STATE.DCS_ENTRY, ACTION.ignore, STATE.DCS_ENTRY); + table.addMany(r(0x20, 0x30), STATE.DCS_ENTRY, ACTION.collect, STATE.DCS_INTERMEDIATE); + table.add(0x3a, STATE.DCS_ENTRY, ACTION.ignore, STATE.DCS_IGNORE); + table.addMany(r(0x30, 0x3a), STATE.DCS_ENTRY, ACTION.param, STATE.DCS_PARAM); + table.add(0x3b, STATE.DCS_ENTRY, ACTION.param, STATE.DCS_PARAM); + table.addMany([0x3c, 0x3d, 0x3e, 0x3f], STATE.DCS_ENTRY, ACTION.collect, STATE.DCS_PARAM); + table.addMany(EXECUTABLES, STATE.DCS_IGNORE, ACTION.ignore, STATE.DCS_IGNORE); + table.addMany(r(0x20, 0x80), STATE.DCS_IGNORE, ACTION.ignore, STATE.DCS_IGNORE); + table.addMany(r(0x1c, 0x20), STATE.DCS_IGNORE, ACTION.ignore, STATE.DCS_IGNORE); + table.addMany(EXECUTABLES, STATE.DCS_PARAM, ACTION.ignore, STATE.DCS_PARAM); + table.add(0x7f, STATE.DCS_PARAM, ACTION.ignore, STATE.DCS_PARAM); + table.addMany(r(0x1c, 0x20), STATE.DCS_PARAM, ACTION.ignore, STATE.DCS_PARAM); + table.addMany(r(0x30, 0x3a), STATE.DCS_PARAM, ACTION.param, STATE.DCS_PARAM); + table.add(0x3b, STATE.DCS_PARAM, ACTION.param, STATE.DCS_PARAM); + table.addMany([0x3a, 0x3c, 0x3d, 0x3e, 0x3f], STATE.DCS_PARAM, ACTION.ignore, STATE.DCS_IGNORE); + table.addMany(r(0x20, 0x30), STATE.DCS_PARAM, ACTION.collect, STATE.DCS_INTERMEDIATE); + table.addMany(EXECUTABLES, STATE.DCS_INTERMEDIATE, ACTION.ignore, STATE.DCS_INTERMEDIATE); + table.add(0x7f, STATE.DCS_INTERMEDIATE, ACTION.ignore, STATE.DCS_INTERMEDIATE); + table.addMany(r(0x1c, 0x20), STATE.DCS_INTERMEDIATE, ACTION.ignore, STATE.DCS_INTERMEDIATE); + table.addMany(r(0x20, 0x30), STATE.DCS_INTERMEDIATE, ACTION.collect, STATE.DCS_INTERMEDIATE); + table.addMany(r(0x30, 0x40), STATE.DCS_INTERMEDIATE, ACTION.ignore, STATE.DCS_IGNORE); + table.addMany(r(0x40, 0x7f), STATE.DCS_INTERMEDIATE, ACTION.dcs_hook, STATE.DCS_PASSTHROUGH); + table.addMany(r(0x40, 0x7f), STATE.DCS_PARAM, ACTION.dcs_hook, STATE.DCS_PASSTHROUGH); + table.addMany(r(0x40, 0x7f), STATE.DCS_ENTRY, ACTION.dcs_hook, STATE.DCS_PASSTHROUGH); + table.addMany(EXECUTABLES, STATE.DCS_PASSTHROUGH, ACTION.dcs_put, STATE.DCS_PASSTHROUGH); + table.addMany(PRINTABLES, STATE.DCS_PASSTHROUGH, ACTION.dcs_put, STATE.DCS_PASSTHROUGH); + table.add(0x7f, STATE.DCS_PASSTHROUGH, ACTION.ignore, STATE.DCS_PASSTHROUGH); + table.addMany([0x1b, 0x9c], STATE.DCS_PASSTHROUGH, ACTION.dcs_unhook, STATE.GROUND); + + return table; })(); -export class AnsiParser { + +// default transition table points to global object +// Q: Copy table to allow custom sequences w'o changing global object? +export class EscapeSequenceParser { public initialState: number; public currentState: number; public transitions: TransitionTable; @@ -163,29 +227,34 @@ export class AnsiParser { public params: number[]; public collected: string; public term: any; - constructor(terminal: IParserTerminal) { - this.initialState = 0; - this.currentState = this.initialState | 0; - this.transitions = new TransitionTable(4095); - this.transitions.table.set(TRANSITION_TABLE.table); + constructor( + terminal?: IParserTerminal | any, + transitions: TransitionTable = VT500_TRANSITION_TABLE) + { + this.initialState = STATE.GROUND; + this.currentState = this.initialState; + this.transitions = transitions; this.osc = ''; this.params = [0]; this.collected = ''; this.term = terminal || {}; - let instructions = ['inst_p', 'inst_o', 'inst_x', 'inst_c', - 'inst_e', 'inst_H', 'inst_P', 'inst_U', 'inst_E']; + let instructions = [ + 'actionPrint', 'actionOSC', 'actionExecute', 'actionCSI', 'actionESC', + 'actionDCSHook', 'actionDCSPrint', 'actionDCSUnhook', 'actionError']; for (let i = 0; i < instructions.length; ++i) { if (!(instructions[i] in this.term)) { this.term[instructions[i]] = function(): void {}; } } } + reset(): void { this.currentState = this.initialState; this.osc = ''; this.params = [0]; this.collected = ''; } + parse(s: string): void { let code = 0; let transition = 0; @@ -204,79 +273,81 @@ export class AnsiParser { let l = s.length; for (let i = 0; i < l; ++i) { code = s.charCodeAt(i); + // shortcut for most chars (print action) - if (currentState === 0 && (code > 0x1f && code < 0x80)) { + if (currentState === STATE.GROUND && (code > 0x1f && code < 0x80)) { printed = (~printed) ? printed : i; continue; } - if (currentState === 4) { - if (code === 0x3b) { - params.push(0); - continue; - } - if (code > 0x2f && code < 0x39) { - params[params.length - 1] = params[params.length - 1] * 10 + code - 48; - continue; - } + + // shortcut for CSI params + if (currentState === STATE.CSI_PARAM && (code > 0x2f && code < 0x39)) { + params[params.length - 1] = params[params.length - 1] * 10 + code - 48; + continue; } - transition = ((code < 0xa0) ? (table[currentState << 8 | code]) : 16); + + // normal transition & action lookup + transition = (code < 0xa0) ? (table[currentState << 8 | code]) : DEFAULT_TRANSITION; switch (transition >> 4) { - case 2: // print + case ACTION.print: printed = (~printed) ? printed : i; break; - case 3: // execute - if (printed + 1) { - this.term.inst_p(s, printed, i); + case ACTION.execute: + if (~printed) { + this.term.actionPrint(s, printed, i); printed = -1; } - this.term.inst_x(String.fromCharCode(code)); + this.term.actionExecute(String.fromCharCode(code)); break; - case 0: // ignore - // handle leftover print and dcs chars - if (printed + 1) { - this.term.inst_p(s, printed, i); + case ACTION.ignore: + // handle leftover print or dcs chars + if (~printed) { + this.term.actionPrint(s, printed, i); printed = -1; - } else if (dcs + 1) { - this.term.inst_P(s.substring(dcs, i)); + } else if (~dcs) { + this.term.actionDCSPrint(s, dcs, i); dcs = -1; } break; - case 1: // error - // handle unicode chars in write buffers w'o state change + case ACTION.error: + // chars higher than 0x9f are handled by this action to + // keep the lookup table small if (code > 0x9f) { switch (currentState) { - case 0: // GROUND -> add char to print string + case STATE.GROUND: // add char to print string printed = (~printed) ? printed : i; break; - case 8: // OSC_STRING -> add char to osc string + case STATE.OSC_STRING: // add char to osc string osc += String.fromCharCode(code); - transition |= 8; + transition |= STATE.OSC_STRING; break; - case 6: // CSI_IGNORE -> ignore char - transition |= 6; + case STATE.CSI_IGNORE: // ignore char + transition |= STATE.CSI_IGNORE; break; - case 11: // DCS_IGNORE -> ignore char - transition |= 11; + case STATE.DCS_IGNORE: // ignore char + transition |= STATE.DCS_IGNORE; break; - case 13: // DCS_PASSTHROUGH -> add char to dcs - if (!(~dcs)) dcs = i | 0; - transition |= 13; + case STATE.DCS_PASSTHROUGH: // add char to dcs string + dcs = (~dcs) ? dcs : i; + transition |= STATE.DCS_PASSTHROUGH; break; - default: // real error + default: error = true; } - } else { // real error + } else { error = true; } + // if we end up here a real error happened + // FIXME: eval and inject return values if (error) { - if (this.term.inst_E( + if (this.term.actionError( { - pos: i, // position in parse string - character: String.fromCharCode(code), // wrong character - state: currentState, // in state - print: printed, // print buffer - dcs: dcs, // dcs buffer - osc: osc, // osc buffer + pos: i, // position in string + code: code, // actual character code + state: currentState, // current state + print: printed, // print buffer start index + dcs: dcs, // dcs buffer start index + osc: osc, // osc string buffer collect: collected, // collect buffer params: params // params buffer })) { @@ -285,22 +356,22 @@ export class AnsiParser { error = false; } break; - case 7: // csi_dispatch - this.term.inst_c(collected, params, String.fromCharCode(code)); + case ACTION.csi_dispatch: + this.term.actionCSI(collected, params, String.fromCharCode(code)); break; - case 8: // param + case ACTION.param: if (code === 0x3b) params.push(0); else params[params.length - 1] = params[params.length - 1] * 10 + code - 48; break; - case 9: // collect + case ACTION.collect: collected += String.fromCharCode(code); break; - case 10: // esc_dispatch - this.term.inst_e(collected, String.fromCharCode(code)); + case ACTION.esc_dispatch: + this.term.actionESC(collected, String.fromCharCode(code)); break; - case 11: // clear + case ACTION.clear: if (~printed) { - this.term.inst_p(s, printed, i); + this.term.actionPrint(s, printed, i); printed = -1; } osc = ''; @@ -308,34 +379,34 @@ export class AnsiParser { collected = ''; dcs = -1; break; - case 12: // dcs_hook - this.term.inst_H(collected, params, String.fromCharCode(code)); + case ACTION.dcs_hook: + this.term.actionDCSHook(collected, params, String.fromCharCode(code)); break; - case 13: // dcs_put - if (!(~dcs)) dcs = i; + case ACTION.dcs_put: + dcs = (~dcs) ? dcs : i; break; - case 14: // dcs_unhook - if (~dcs) this.term.inst_P(s.substring(dcs, i)); - this.term.inst_U(); - if (code === 0x1b) transition |= 1; + case ACTION.dcs_unhook: + if (~dcs) this.term.actionDCSPrint(s, dcs, i); + this.term.actionDCSUnhook(); + if (code === 0x1b) transition |= STATE.ESCAPE; osc = ''; params = [0]; collected = ''; dcs = -1; break; - case 4: // osc_start + case ACTION.osc_start: if (~printed) { - this.term.inst_p(s, printed, i); + this.term.actionPrint(s, printed, i); printed = -1; } osc = ''; break; - case 5: // osc_put + case ACTION.osc_put: osc += s.charAt(i); break; - case 6: // osc_end - if (osc && code !== 0x18 && code !== 0x1a) this.term.inst_o(osc); - if (code === 0x1b) transition |= 1; + case ACTION.osc_end: + if (osc && code !== 0x18 && code !== 0x1a) this.term.actionOSC(osc); + if (code === 0x1b) transition |= STATE.ESCAPE; osc = ''; params = [0]; collected = ''; @@ -346,10 +417,10 @@ export class AnsiParser { } // push leftover pushable buffers to terminal - if (!currentState && (printed + 1)) { - this.term.inst_p(s, printed, s.length); - } else if (currentState === 13 && (dcs + 1)) { - this.term.inst_P(s.substring(dcs)); + if (currentState === STATE.GROUND && ~printed) { + this.term.actionPrint(s, printed, s.length); + } else if (currentState === STATE.DCS_PASSTHROUGH && ~dcs) { + this.term.actionDCSPrint(s, dcs, s.length); } // save non pushable buffers @@ -363,31 +434,28 @@ export class AnsiParser { } - - - -import { IInputHandler, IInputHandlingTerminal } from './Types'; -import { CHARSETS, DEFAULT_CHARSET } from './Charsets'; -import { C0 } from './EscapeSequences'; - // 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: AnsiParser; + private _parser: EscapeSequenceParser; private _terminal: any; private _inputHandler: IInputHandler; - constructor(_terminal: any, _inputHandler: IInputHandler) { - this._parser = new AnsiParser(this); + constructor(_inputHandler: IInputHandler, _terminal: any) { + this._parser = new EscapeSequenceParser(this); this._terminal = _terminal; this._inputHandler = _inputHandler; } - write(data: string): void { + parse(data: string): void { const cursorStartX = this._terminal.buffer.x; const cursorStartY = this._terminal.buffer.y; if (this._terminal.debug) { this._terminal.log('data: ' + data); } + // apply leftover surrogate high from last write if (this._terminal.surrogate_high) { data = this._terminal.surrogate_high + data; @@ -401,8 +469,7 @@ export class ParserTerminal implements IParserTerminal { } } - inst_p(data: string, start: number, end: number): void { - // const l = data.length; + actionPrint(data: string, start: number, end: number): void { let ch; let code; let low; @@ -429,14 +496,19 @@ export class ParserTerminal implements IParserTerminal { } } - inst_o(data: string): void { - let params = data.split(';'); - switch (parseInt(params[0])) { + 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 (params[1]) { - this._terminal.title = params[1]; + if (content) { + this._terminal.title = content; this._terminal.handleTitle(this._terminal.title); } break; @@ -487,11 +559,13 @@ export class ParserTerminal implements IParserTerminal { } } - inst_x(flag: string): void { + 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: return this._inputHandler.lineFeed(); - case C0.VT: return this._inputHandler.lineFeed(); + 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(); @@ -504,7 +578,7 @@ export class ParserTerminal implements IParserTerminal { this._terminal.error('Unknown EXEC flag: %s.', flag); } - inst_c(collected: string, params: number[], flag: string): void { + actionCSI(collected: string, params: number[], flag: string): void { this._terminal.prefix = collected; switch (flag) { case '@': return this._inputHandler.insertChars(params); @@ -524,6 +598,7 @@ export class ParserTerminal implements IParserTerminal { 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); } @@ -559,33 +634,26 @@ export class ParserTerminal implements IParserTerminal { this._terminal.error('Unknown CSI code: %s %s %s.', collected, params, flag); } - inst_e(collected: string, flag: string): void { - let cs; - + 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) - this._inputHandler.saveCursor(); - return; + return this._inputHandler.saveCursor(); case '8': // Restore Cursor (DECRC) - this._inputHandler.restoreCursor(); - return; + return this._inputHandler.restoreCursor(); // case '9': // Forward Index (DECFI), VT420 and up - not supported case 'D': // Index (IND is 0x84) - this._terminal.index(); - return; + 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) - (this._terminal).tabSet(); - return; + return (this._terminal).tabSet(); case 'M': // Reverse Index (RI is 0x8d) - this._terminal.reverseIndex(); - return; + 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; @@ -620,20 +688,15 @@ export class ParserTerminal implements IParserTerminal { // 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). - this._terminal.setgLevel(2); - return; + return this._terminal.setgLevel(2); case 'o': // Invoke the G3 Character Set as GL (LS3). - this._terminal.setgLevel(3); - return; + return this._terminal.setgLevel(3); case '|': // Invoke the G3 Character Set as GR (LS3R). - this._terminal.setgLevel(3); - return; + return this._terminal.setgLevel(3); case '}': // Invoke the G2 Character Set as GR (LS2R). - this._terminal.setgLevel(2); - return; + return this._terminal.setgLevel(2); case '~': // Invoke the G1 Character Set as GR (LS1R). - this._terminal.setgLevel(1); - return; + return this._terminal.setgLevel(1); } // case ' ': // switch (flag) { @@ -664,55 +727,59 @@ export class ParserTerminal implements IParserTerminal { // load character sets case '(': // G0 (VT100) - cs = CHARSETS[flag]; - if (!cs) cs = DEFAULT_CHARSET; - this._terminal.setgCharset(0, cs); - return; + return this._terminal.setgCharset(0, CHARSETS[flag] || DEFAULT_CHARSET); case ')': // G1 (VT100) - cs = CHARSETS[flag]; - if (!cs) cs = DEFAULT_CHARSET; - this._terminal.setgCharset(1, cs); - return; + return this._terminal.setgCharset(1, CHARSETS[flag] || DEFAULT_CHARSET); case '*': // G2 (VT220) - cs = CHARSETS[flag]; - if (!cs) cs = DEFAULT_CHARSET; - this._terminal.setgCharset(2, cs); - return; + return this._terminal.setgCharset(2, CHARSETS[flag] || DEFAULT_CHARSET); case '+': // G3 (VT220) - cs = CHARSETS[flag]; - if (!cs) cs = DEFAULT_CHARSET; - this._terminal.setgCharset(3, cs); - return; + return this._terminal.setgCharset(3, CHARSETS[flag] || DEFAULT_CHARSET); case '-': // G1 (VT300) - cs = CHARSETS[flag]; - if (!cs) cs = DEFAULT_CHARSET; - this._terminal.setgCharset(1, cs); - return; + return this._terminal.setgCharset(1, CHARSETS[flag] || DEFAULT_CHARSET); case '.': // G2 (VT300) - if (!cs) cs = DEFAULT_CHARSET; - this._terminal.setgCharset(2, cs); - return; + return this._terminal.setgCharset(2, CHARSETS[flag] || DEFAULT_CHARSET); case '/': // G3 (VT300) - // not supported - how to deal with this? (original code is not reachable) + // 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); } } - inst_H(collected: string, params: number[], flag: string): void { + 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 } - inst_P(dcs: string): void { + unregisterOSCHandler(): void { // TODO } - inst_U(): void { + registerDCSHandler(): void { // TODO } - inst_E(): void { + unregisterDCSHandler(): void { // TODO } } diff --git a/src/Terminal.ts b/src/Terminal.ts index 44c9064333..54548f9ef8 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -334,7 +334,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this._inputHandler = new InputHandler(this); // this._parser = new Parser(this._inputHandler, this); - this._newParser = new ParserTerminal(this, this._inputHandler); + this._newParser = new ParserTerminal(this._inputHandler, this); // Reuse renderer if the Terminal is being recreated via a reset call. this.renderer = this.renderer || null; this.selectionManager = this.selectionManager || null; @@ -1320,7 +1320,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // just sets the state back based on the correct return statement. // const state = this._parser.parse(data); - this._newParser.write(data); + this._newParser.parse(data); // this._parser.setState(state); this.updateRange(this.buffer.y); From f9767073ba214040d179303aa781f72fb895cd99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sat, 21 Apr 2018 05:12:24 +0200 Subject: [PATCH 04/44] new parser --- src/EscapeSequenceParser.test.ts | 1042 ++++++++++++++++++++++++++++++ src/EscapeSequenceParser.ts | 718 ++++++++++++++++++++ src/InputHandler.ts | 4 +- src/Terminal.ts | 15 +- 4 files changed, 1772 insertions(+), 7 deletions(-) create mode 100644 src/EscapeSequenceParser.test.ts create mode 100644 src/EscapeSequenceParser.ts diff --git a/src/EscapeSequenceParser.test.ts b/src/EscapeSequenceParser.test.ts new file mode 100644 index 0000000000..eefa760356 --- /dev/null +++ b/src/EscapeSequenceParser.test.ts @@ -0,0 +1,1042 @@ +import { AnsiParser, IParserTerminal } 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 + }, + inst_p: function (s: string, start: number, end: number): void { + this.calls.push(['print', s.substring(start, end)]); + }, + inst_o: function (s: string): void { + this.calls.push(['osc', s]); + }, + inst_x: function (flag: string): void { + this.calls.push(['exe', flag]); + }, + inst_c: function (collected: string, params: number[], flag: string): void { + this.calls.push(['csi', collected, params, flag]); + }, + inst_e: function (collected: string, flag: string): void { + this.calls.push(['esc', collected, flag]); + }, + inst_H: function (collected: string, params: number[], flag: string): void { + this.calls.push(['dcs hook', collected, params, flag]); + }, + inst_P: function (dcs: string): void { + this.calls.push(['dcs put', dcs]); + }, + inst_U: function (): void { + this.calls.push(['dcs unhook']); + } +}; + +let parser = new AnsiParser(testTerminal); + +describe('Parser init and methods', function(): void { + it('parser init', function (): void { + let p = new AnsiParser({}); + chai.expect(p.term).a('object'); + chai.expect(p.term.inst_p).a('function'); + chai.expect(p.term.inst_o).a('function'); + chai.expect(p.term.inst_x).a('function'); + chai.expect(p.term.inst_c).a('function'); + chai.expect(p.term.inst_e).a('function'); + chai.expect(p.term.inst_H).a('function'); + chai.expect(p.term.inst_P).a('function'); + chai.expect(p.term.inst_U).a('function'); + p.parse('\x1b[31mHello World!'); + }); + it('terminal callbacks', function (): void { + chai.expect(parser.term).equal(testTerminal); + chai.expect(parser.term.inst_p).equal(testTerminal.inst_p); + chai.expect(parser.term.inst_o).equal(testTerminal.inst_o); + chai.expect(parser.term.inst_x).equal(testTerminal.inst_x); + chai.expect(parser.term.inst_c).equal(testTerminal.inst_c); + chai.expect(parser.term.inst_e).equal(testTerminal.inst_e); + chai.expect(parser.term.inst_H).equal(testTerminal.inst_H); + chai.expect(parser.term.inst_P).equal(testTerminal.inst_P); + chai.expect(parser.term.inst_U).equal(testTerminal.inst_U); + }); + 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(0); + 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 = 0; + parser.parse(exes[i]); + chai.expect(parser.currentState).equal(0); + 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 = 0; + parser.parse(printables[i]); + chai.expect(parser.currentState).equal(0); + 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 (let state = 0; state < 14; ++state) { + for (let i = 0; i < exes.length; ++i) { + parser.currentState = state; + parser.parse(exes[i]); + chai.expect(parser.currentState).equal(0); + testTerminal.compare(((exceptions[state]) ? exceptions[state][exes[i]] : 0) || [['exe', exes[i]]]); + parser.reset(); + testTerminal.clear(); + } + parser.parse('\x9c'); + chai.expect(parser.currentState).equal(0); + testTerminal.compare([]); + parser.reset(); + testTerminal.clear(); + } + }); + it('trans ANYWHERE --> ESCAPE with clear', function (): void { + parser.reset(); + for (let state = 0; state < 14; ++state) { + parser.currentState = state; + parser.osc = '#'; + parser.params = [23]; + parser.collected = '#'; + parser.parse('\x1b'); + chai.expect(parser.currentState).equal(1); + 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 = 1; + parser.parse(exes[i]); + chai.expect(parser.currentState).equal(1); + testTerminal.compare([['exe', exes[i]]]); + parser.reset(); + testTerminal.clear(); + } + }); + it('state ESCAPE ignore', function (): void { + parser.reset(); + testTerminal.clear(); + parser.currentState = 1; + parser.parse('\x7f'); + chai.expect(parser.currentState).equal(1); + 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 = 1; + parser.parse(dispatches[i]); + chai.expect(parser.currentState).equal(0); + 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 = 1; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(2); + 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 = 2; + parser.parse(exes[i]); + chai.expect(parser.currentState).equal(2); + testTerminal.compare([['exe', exes[i]]]); + parser.reset(); + testTerminal.clear(); + } + }); + it('state ESCAPE_INTERMEDIATE ignore', function (): void { + parser.reset(); + testTerminal.clear(); + parser.currentState = 2; + parser.parse('\x7f'); + chai.expect(parser.currentState).equal(2); + 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 = 2; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(2); + 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 = 2; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(0); + testTerminal.compare([['esc', '', collect[i]]]); + parser.reset(); + testTerminal.clear(); + } + }); + it('trans ANYWHERE/ESCAPE --> CSI_ENTRY with clear', function (): void { + parser.reset(); + // C0 + parser.currentState = 1; + parser.osc = '#'; + parser.params = [123]; + parser.collected = '#'; + parser.parse('['); + chai.expect(parser.currentState).equal(3); + chai.expect(parser.osc).equal(''); + chai.expect(parser.params).eql([0]); + chai.expect(parser.collected).equal(''); + parser.reset(); + // C1 + for (let state = 0; state < 14; ++state) { + parser.currentState = state; + parser.osc = '#'; + parser.params = [123]; + parser.collected = '#'; + parser.parse('\x9b'); + chai.expect(parser.currentState).equal(3); + 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 = 3; + parser.parse(exes[i]); + chai.expect(parser.currentState).equal(3); + testTerminal.compare([['exe', exes[i]]]); + parser.reset(); + testTerminal.clear(); + } + }); + it('state CSI_ENTRY ignore', function (): void { + parser.reset(); + testTerminal.clear(); + parser.currentState = 3; + parser.parse('\x7f'); + chai.expect(parser.currentState).equal(3); + 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 = 3; + parser.parse(dispatches[i]); + chai.expect(parser.currentState).equal(0); + 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 = 3; + parser.parse(params[i]); + chai.expect(parser.currentState).equal(4); + chai.expect(parser.params).eql([params[i].charCodeAt(0) - 48]); + parser.reset(); + } + // ';' + parser.currentState = 3; + parser.parse('\x3b'); + chai.expect(parser.currentState).equal(4); + chai.expect(parser.params).eql([0, 0]); + parser.reset(); + for (let i = 0; i < collect.length; ++i) { + parser.currentState = 3; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(4); + 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 = 4; + parser.parse(exes[i]); + chai.expect(parser.currentState).equal(4); + 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 = 4; + parser.parse(params[i]); + chai.expect(parser.currentState).equal(4); + chai.expect(parser.params).eql([params[i].charCodeAt(0) - 48]); + parser.reset(); + } + parser.currentState = 4; + parser.parse('\x3b'); + chai.expect(parser.currentState).equal(4); + chai.expect(parser.params).eql([0, 0]); + parser.reset(); + }); + it('state CSI_PARAM ignore', function (): void { + parser.reset(); + testTerminal.clear(); + parser.currentState = 4; + parser.parse('\x7f'); + chai.expect(parser.currentState).equal(4); + 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 = 4; + parser.params = [0, 1]; + parser.parse(dispatches[i]); + chai.expect(parser.currentState).equal(0); + 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 = 3; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(5); + 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 = 4; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(5); + 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 = 5; + parser.parse(exes[i]); + chai.expect(parser.currentState).equal(5); + 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 = 5; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(5); + chai.expect(parser.collected).equal(collect[i]); + parser.reset(); + } + }); + it('state CSI_INTERMEDIATE ignore', function (): void { + parser.reset(); + testTerminal.clear(); + parser.currentState = 5; + parser.parse('\x7f'); + chai.expect(parser.currentState).equal(5); + 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 = 5; + parser.params = [0, 1]; + parser.parse(dispatches[i]); + chai.expect(parser.currentState).equal(0); + testTerminal.compare([['csi', '', [0, 1], dispatches[i]]]); + parser.reset(); + testTerminal.clear(); + } + }); + it('trans CSI_ENTRY --> CSI_IGNORE', function (): void { + parser.reset(); + parser.currentState = 3; + parser.parse('\x3a'); + chai.expect(parser.currentState).equal(6); + 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 = 4; + parser.parse('\x3b' + chars[i]); + chai.expect(parser.currentState).equal(6); + 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 = 5; + parser.parse(chars[i]); + chai.expect(parser.currentState).equal(6); + 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 = 6; + parser.parse(exes[i]); + chai.expect(parser.currentState).equal(6); + 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 = 6; + parser.parse(ignored[i]); + chai.expect(parser.currentState).equal(6); + 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 = 6; + parser.params = [0, 1]; + parser.parse(dispatches[i]); + chai.expect(parser.currentState).equal(0); + 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(7); + parser.reset(); + } + // C1 + for (let state = 0; state < 14; ++state) { + parser.currentState = state; + initializers = ['\x98', '\x9e', '\x9f']; + for (let i = 0; i < initializers.length; ++i) { + parser.parse(initializers[i]); + chai.expect(parser.currentState).equal(7); + 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 = 7; + parser.parse(ignored[i]); + chai.expect(parser.currentState).equal(7); + parser.reset(); + } + }); + it('trans ANYWHERE/ESCAPE --> OSC_STRING', function (): void { + parser.reset(); + // C0 + parser.parse('\x1b]'); + chai.expect(parser.currentState).equal(8); + parser.reset(); + // C1 + for (let state = 0; state < 14; ++state) { + parser.currentState = state; + parser.parse('\x9d'); + chai.expect(parser.currentState).equal(8); + 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 = 8; + parser.parse(ignored[i]); + chai.expect(parser.currentState).equal(8); + 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 = 8; + parser.parse(puts[i]); + chai.expect(parser.currentState).equal(8); + 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(9); + parser.reset(); + // C1 + for (let state = 0; state < 14; ++state) { + parser.currentState = state; + parser.parse('\x90'); + chai.expect(parser.currentState).equal(9); + 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 = 9; + parser.parse(ignored[i]); + chai.expect(parser.currentState).equal(9); + 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 = 9; + parser.parse(params[i]); + chai.expect(parser.currentState).equal(10); + chai.expect(parser.params).eql([params[i].charCodeAt(0) - 48]); + parser.reset(); + } + parser.currentState = 9; + parser.parse('\x3b'); + chai.expect(parser.currentState).equal(10); + chai.expect(parser.params).eql([0, 0]); + parser.reset(); + for (let i = 0; i < collect.length; ++i) { + parser.currentState = 9; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(10); + 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 = 10; + parser.parse(ignored[i]); + chai.expect(parser.currentState).equal(10); + 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 = 10; + parser.parse(params[i]); + chai.expect(parser.currentState).equal(10); + chai.expect(parser.params).eql([params[i].charCodeAt(0) - 48]); + parser.reset(); + } + parser.currentState = 10; + parser.parse('\x3b'); + chai.expect(parser.currentState).equal(10); + chai.expect(parser.params).eql([0, 0]); + parser.reset(); + }); + it('trans DCS_ENTRY --> DCS_IGNORE', function (): void { + parser.reset(); + parser.currentState = 9; + parser.parse('\x3a'); + chai.expect(parser.currentState).equal(11); + 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 = 10; + parser.parse('\x3b' + chars[i]); + chai.expect(parser.currentState).equal(11); + 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 = 12; + parser.parse(chars[i]); + chai.expect(parser.currentState).equal(11); + 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 = 11; + parser.parse(ignored[i]); + chai.expect(parser.currentState).equal(11); + 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 = 9; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(12); + 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 = 10; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(12); + 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 = 12; + parser.parse(ignored[i]); + chai.expect(parser.currentState).equal(12); + 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 = 12; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(12); + 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 = 12; + parser.parse('\x20' + chars[i]); + chai.expect(parser.currentState).equal(11); + 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 = 9; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(13); + 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 = 10; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(13); + 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 = 12; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(13); + 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 = 13; + parser.parse(puts[i]); + chai.expect(parser.currentState).equal(13); + testTerminal.compare([['dcs put', puts[i]]]); + parser.reset(); + testTerminal.clear(); + } + }); + it('state DCS_PASSTHROUGH ignore', function (): void { + parser.reset(); + testTerminal.clear(); + parser.currentState = 13; + parser.parse('\x7f'); + chai.expect(parser.currentState).equal(13); + 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 = 6; + parser.parse('€öäü'); + chai.expect(parser.currentState).equal(6); + testTerminal.compare([]); + parser.reset(); + testTerminal.clear(); + }); + it('DCS_IGNORE error', function (): void { + parser.reset(); + testTerminal.clear(); + parser.currentState = 11; + parser.parse('€öäü'); + chai.expect(parser.currentState).equal(11); + testTerminal.compare([]); + parser.reset(); + testTerminal.clear(); + }); + it('DCS_PASSTHROUGH error', function (): void { + parser.reset(); + testTerminal.clear(); + parser.currentState = 13; + parser.parse('€öäü'); + chai.expect(parser.currentState).equal(13); + testTerminal.compare([['dcs put', '€öäü']]); + parser.reset(); + testTerminal.clear(); + }); + it('error else of if (code > 159)', function (): void { + parser.reset(); + testTerminal.clear(); + parser.currentState = 0; + parser.parse('\x1e'); + chai.expect(parser.currentState).equal(0); + testTerminal.compare([]); + parser.reset(); + testTerminal.clear(); + }); +}); + +let errorTerminal1 = function(): void {}; +errorTerminal1.prototype = testTerminal; +let errTerminal1 = new errorTerminal1(); +errTerminal1.inst_E = function(e: any): void { + this.calls.push(['error', e]); +}; +let errParser1 = new AnsiParser(errTerminal1); + +let errorTerminal2 = function(): void {}; +errorTerminal2.prototype = testTerminal; +let errTerminal2 = new errorTerminal2(); +errTerminal2.inst_E = function(e: any): any { + this.calls.push(['error', e]); + return true; // --> abort parsing +}; +let errParser2 = new AnsiParser(errTerminal2); + +describe('error tests', function(): void { + it('CSI_PARAM unicode error - inst_E output w/o abort', function (): void { + errParser1.parse('\x1b[<31;5€normal print'); + errTerminal1.compare([ + ['error', { + pos: 7, + character: '€', + state: 4, + print: -1, + dcs: -1, + osc: '', + collect: '<', + params: [31, 5]}], + ['print', 'normal print'] + ]); + parser.reset(); + testTerminal.clear(); + }); + it('CSI_PARAM unicode error - inst_E output with abort', function (): void { + errParser2.parse('\x1b[<31;5€no print'); + errTerminal2.compare([ + ['error', { + pos: 7, + character: '€', + state: 4, + print: -1, + dcs: -1, + osc: '', + collect: '<', + params: [31, 5]}] + ]); + parser.reset(); + testTerminal.clear(); + }); +}); diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts new file mode 100644 index 0000000000..6facddeab8 --- /dev/null +++ b/src/EscapeSequenceParser.ts @@ -0,0 +1,718 @@ +export interface IParserTerminal { + inst_p?: (s: string, start: number, end: number) => void; + inst_o?: (s: string) => void; + inst_x?: (flag: string) => void; + inst_c?: (collected: string, params: number[], flag: string) => void; + inst_e?: (collected: string, flag: string) => void; + inst_H?: (collected: string, params: number[], flag: string) => void; + inst_P?: (dcs: string) => void; + inst_U?: () => void; + inst_E?: () => void; // TODO: real signature +} + + +export function r(a: number, b: number): number[] { + let c = b - a; + let arr = new Array(c); + while (c--) { + arr[c] = --b; + } + return arr; +} + + +export class TransitionTable { + public table: Uint8Array; + constructor(length: number) { + this.table = new Uint8Array(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_list(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); + } + } +} + + +let PRINTABLES = r(0x20, 0x7f); +let EXECUTABLES = r(0x00, 0x18); +EXECUTABLES.push(0x19); +EXECUTABLES.concat(r(0x1c, 0x20)); + + +export const TRANSITION_TABLE = (function (): TransitionTable { + let t: TransitionTable = new TransitionTable(4095); + + // table with default transition [any] --> [error, GROUND] + for (let state = 0; state < 14; ++state) { + for (let code = 0; code < 160; ++code) { + t[state << 8 | code] = 16; + } + } + + // apply transitions + // printables + t.add_list(PRINTABLES, 0, 2, 0); + // global anywhere rules + for (let state = 0; state < 14; ++state) { + t.add_list([0x18, 0x1a, 0x99, 0x9a], state, 3, 0); + t.add_list(r(0x80, 0x90), state, 3, 0); + t.add_list(r(0x90, 0x98), state, 3, 0); + t.add(0x9c, state, 0, 0); // ST as terminator + t.add(0x1b, state, 11, 1); // ESC + t.add(0x9d, state, 4, 8); // OSC + t.add_list([0x98, 0x9e, 0x9f], state, 0, 7); + t.add(0x9b, state, 11, 3); // CSI + t.add(0x90, state, 11, 9); // DCS + } + // rules for executables and 7f + t.add_list(EXECUTABLES, 0, 3, 0); + t.add_list(EXECUTABLES, 1, 3, 1); + t.add(0x7f, 1, null, 1); + t.add_list(EXECUTABLES, 8, null, 8); + t.add_list(EXECUTABLES, 3, 3, 3); + t.add(0x7f, 3, null, 3); + t.add_list(EXECUTABLES, 4, 3, 4); + t.add(0x7f, 4, null, 4); + t.add_list(EXECUTABLES, 6, 3, 6); + t.add_list(EXECUTABLES, 5, 3, 5); + t.add(0x7f, 5, null, 5); + t.add_list(EXECUTABLES, 2, 3, 2); + t.add(0x7f, 2, null, 2); + // osc + t.add(0x5d, 1, 4, 8); + t.add_list(PRINTABLES, 8, 5, 8); + t.add(0x7f, 8, 5, 8); + t.add_list([0x9c, 0x1b, 0x18, 0x1a, 0x07], 8, 6, 0); + t.add_list(r(0x1c, 0x20), 8, 0, 8); + // sos/pm/apc does nothing + t.add_list([0x58, 0x5e, 0x5f], 1, 0, 7); + t.add_list(PRINTABLES, 7, null, 7); + t.add_list(EXECUTABLES, 7, null, 7); + t.add(0x9c, 7, 0, 0); + // csi entries + t.add(0x5b, 1, 11, 3); + t.add_list(r(0x40, 0x7f), 3, 7, 0); + t.add_list(r(0x30, 0x3a), 3, 8, 4); + t.add(0x3b, 3, 8, 4); + t.add_list([0x3c, 0x3d, 0x3e, 0x3f], 3, 9, 4); + t.add_list(r(0x30, 0x3a), 4, 8, 4); + t.add(0x3b, 4, 8, 4); + t.add_list(r(0x40, 0x7f), 4, 7, 0); + t.add_list([0x3a, 0x3c, 0x3d, 0x3e, 0x3f], 4, 0, 6); + t.add_list(r(0x20, 0x40), 6, null, 6); + t.add(0x7f, 6, null, 6); + t.add_list(r(0x40, 0x7f), 6, 0, 0); + t.add(0x3a, 3, 0, 6); + t.add_list(r(0x20, 0x30), 3, 9, 5); + t.add_list(r(0x20, 0x30), 5, 9, 5); + t.add_list(r(0x30, 0x40), 5, 0, 6); + t.add_list(r(0x40, 0x7f), 5, 7, 0); + t.add_list(r(0x20, 0x30), 4, 9, 5); + // esc_intermediate + t.add_list(r(0x20, 0x30), 1, 9, 2); + t.add_list(r(0x20, 0x30), 2, 9, 2); + t.add_list(r(0x30, 0x7f), 2, 10, 0); + t.add_list(r(0x30, 0x50), 1, 10, 0); + t.add_list([0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x59, 0x5a, 0x5c], 1, 10, 0); + t.add_list(r(0x60, 0x7f), 1, 10, 0); + // dcs entry + t.add(0x50, 1, 11, 9); + t.add_list(EXECUTABLES, 9, null, 9); + t.add(0x7f, 9, null, 9); + t.add_list(r(0x1c, 0x20), 9, null, 9); + t.add_list(r(0x20, 0x30), 9, 9, 12); + t.add(0x3a, 9, 0, 11); + t.add_list(r(0x30, 0x3a), 9, 8, 10); + t.add(0x3b, 9, 8, 10); + t.add_list([0x3c, 0x3d, 0x3e, 0x3f], 9, 9, 10); + t.add_list(EXECUTABLES, 11, null, 11); + t.add_list(r(0x20, 0x80), 11, null, 11); + t.add_list(r(0x1c, 0x20), 11, null, 11); + t.add_list(EXECUTABLES, 10, null, 10); + t.add(0x7f, 10, null, 10); + t.add_list(r(0x1c, 0x20), 10, null, 10); + t.add_list(r(0x30, 0x3a), 10, 8, 10); + t.add(0x3b, 10, 8, 10); + t.add_list([0x3a, 0x3c, 0x3d, 0x3e, 0x3f], 10, 0, 11); + t.add_list(r(0x20, 0x30), 10, 9, 12); + t.add_list(EXECUTABLES, 12, null, 12); + t.add(0x7f, 12, null, 12); + t.add_list(r(0x1c, 0x20), 12, null, 12); + t.add_list(r(0x20, 0x30), 12, 9, 12); + t.add_list(r(0x30, 0x40), 12, 0, 11); + t.add_list(r(0x40, 0x7f), 12, 12, 13); + t.add_list(r(0x40, 0x7f), 10, 12, 13); + t.add_list(r(0x40, 0x7f), 9, 12, 13); + t.add_list(EXECUTABLES, 13, 13, 13); + t.add_list(PRINTABLES, 13, 13, 13); + t.add(0x7f, 13, null, 13); + t.add_list([0x1b, 0x9c], 13, 14, 0); + + return t; +})(); + +export class AnsiParser { + public initialState: number; + public currentState: number; + public transitions: TransitionTable; + public osc: string; + public params: number[]; + public collected: string; + public term: any; + constructor(terminal: IParserTerminal) { + this.initialState = 0; + this.currentState = this.initialState | 0; + this.transitions = new TransitionTable(4095); + this.transitions.table.set(TRANSITION_TABLE.table); + this.osc = ''; + this.params = [0]; + this.collected = ''; + this.term = terminal || {}; + let instructions = ['inst_p', 'inst_o', 'inst_x', 'inst_c', + 'inst_e', 'inst_H', 'inst_P', 'inst_U', 'inst_E']; + for (let i = 0; i < instructions.length; ++i) { + if (!(instructions[i] in this.term)) { + this.term[instructions[i]] = function(): void {}; + } + } + } + reset(): void { + this.currentState = this.initialState; + this.osc = ''; + this.params = [0]; + this.collected = ''; + } + parse(s: string): void { + let code = 0; + let transition = 0; + let error = false; + let currentState = this.currentState; + + // local buffers + let printed = -1; + let dcs = -1; + let osc = this.osc; + let collected = this.collected; + let params = this.params; + let table: Uint8Array = this.transitions.table; + + // process input string + let l = s.length; + for (let i = 0; i < l; ++i) { + code = s.charCodeAt(i); + // shortcut for most chars (print action) + if (currentState === 0 && (code > 0x1f && code < 0x80)) { + printed = (~printed) ? printed : i; + continue; + } + if (currentState === 4) { + if (code === 0x3b) { + params.push(0); + continue; + } + if (code > 0x2f && code < 0x39) { + params[params.length - 1] = params[params.length - 1] * 10 + code - 48; + continue; + } + } + transition = ((code < 0xa0) ? (table[currentState << 8 | code]) : 16); + switch (transition >> 4) { + case 2: // print + printed = (~printed) ? printed : i; + break; + case 3: // execute + if (printed + 1) { + this.term.inst_p(s, printed, i); + printed = -1; + } + this.term.inst_x(String.fromCharCode(code)); + break; + case 0: // ignore + // handle leftover print and dcs chars + if (printed + 1) { + this.term.inst_p(s, printed, i); + printed = -1; + } else if (dcs + 1) { + this.term.inst_P(s.substring(dcs, i)); + dcs = -1; + } + break; + case 1: // error + // handle unicode chars in write buffers w'o state change + if (code > 0x9f) { + switch (currentState) { + case 0: // GROUND -> add char to print string + printed = (~printed) ? printed : i; + break; + case 8: // OSC_STRING -> add char to osc string + osc += String.fromCharCode(code); + transition |= 8; + break; + case 6: // CSI_IGNORE -> ignore char + transition |= 6; + break; + case 11: // DCS_IGNORE -> ignore char + transition |= 11; + break; + case 13: // DCS_PASSTHROUGH -> add char to dcs + if (!(~dcs)) dcs = i | 0; + transition |= 13; + break; + default: // real error + error = true; + } + } else { // real error + error = true; + } + if (error) { + if (this.term.inst_E( + { + pos: i, // position in parse string + character: String.fromCharCode(code), // wrong character + state: currentState, // in state + print: printed, // print buffer + dcs: dcs, // dcs buffer + osc: osc, // osc buffer + collect: collected, // collect buffer + params: params // params buffer + })) { + return; + } + error = false; + } + break; + case 7: // csi_dispatch + this.term.inst_c(collected, params, String.fromCharCode(code)); + break; + case 8: // param + if (code === 0x3b) params.push(0); + else params[params.length - 1] = params[params.length - 1] * 10 + code - 48; + break; + case 9: // collect + collected += String.fromCharCode(code); + break; + case 10: // esc_dispatch + this.term.inst_e(collected, String.fromCharCode(code)); + break; + case 11: // clear + if (~printed) { + this.term.inst_p(s, printed, i); + printed = -1; + } + osc = ''; + params = [0]; + collected = ''; + dcs = -1; + break; + case 12: // dcs_hook + this.term.inst_H(collected, params, String.fromCharCode(code)); + break; + case 13: // dcs_put + if (!(~dcs)) dcs = i; + break; + case 14: // dcs_unhook + if (~dcs) this.term.inst_P(s.substring(dcs, i)); + this.term.inst_U(); + if (code === 0x1b) transition |= 1; + osc = ''; + params = [0]; + collected = ''; + dcs = -1; + break; + case 4: // osc_start + if (~printed) { + this.term.inst_p(s, printed, i); + printed = -1; + } + osc = ''; + break; + case 5: // osc_put + osc += s.charAt(i); + break; + case 6: // osc_end + if (osc && code !== 0x18 && code !== 0x1a) this.term.inst_o(osc); + if (code === 0x1b) transition |= 1; + osc = ''; + params = [0]; + collected = ''; + dcs = -1; + break; + } + currentState = transition & 15; + } + + // push leftover pushable buffers to terminal + if (!currentState && (printed + 1)) { + this.term.inst_p(s, printed, s.length); + } else if (currentState === 13 && (dcs + 1)) { + this.term.inst_P(s.substring(dcs)); + } + + // save non pushable buffers + this.osc = osc; + this.collected = collected; + this.params = params; + + // save state + this.currentState = currentState; + } +} + + + + + +import { IInputHandler, IInputHandlingTerminal } from './Types'; +import { CHARSETS, DEFAULT_CHARSET } from './Charsets'; +import { C0 } from './EscapeSequences'; + +// glue code between AnsiParser and Terminal +export class ParserTerminal implements IParserTerminal { + private _parser: AnsiParser; + private _terminal: any; + private _inputHandler: IInputHandler; + + constructor(_terminal: any, _inputHandler: IInputHandler) { + this._parser = new AnsiParser(this); + this._terminal = _terminal; + this._inputHandler = _inputHandler; + } + + write(data: string): void { + const cursorStartX = this._terminal.buffer.x; + const cursorStartY = this._terminal.buffer.y; + if (this._terminal.debug) { + this._terminal.log('data: ' + data); + } + // apply leftover surrogate high from last write + if (this._terminal.surrogate_high) { + data = this._terminal.surrogate_high + data; + this._terminal.surrogate_high = ''; + } + + this._parser.parse(data); + + if (this._terminal.buffer.x !== cursorStartX || this._terminal.buffer.y !== cursorStartY) { + this._terminal.emit('cursormove'); + } + } + + inst_p(data: string, start: number, end: number): void { + // const l = data.length; + let ch; + let code; + let low; + for (let i = start; i < end; ++i) { + ch = data.charAt(i); + code = data.charCodeAt(i); + if (0xD800 <= code && code <= 0xDBFF) { + // we got a surrogate high + // get surrogate low (next 2 bytes) + low = data.charCodeAt(i + 1); + if (isNaN(low)) { + // end of data stream, save surrogate high + this._terminal.surrogate_high = ch; + continue; + } + code = ((code - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000; + ch += data.charAt(i + 1); + } + // surrogate low - already handled above + if (0xDC00 <= code && code <= 0xDFFF) { + continue; + } + this._inputHandler.addChar(ch, code); + } + } + + inst_o(data: string): void { + let params = data.split(';'); + switch (parseInt(params[0])) { + case 0: + case 1: + case 2: + if (params[1]) { + this._terminal.title = params[1]; + 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; + } + } + + inst_x(flag: string): void { + switch (flag) { + case C0.BEL: return this._inputHandler.bell(); + case C0.LF: return this._inputHandler.lineFeed(); + case C0.VT: return this._inputHandler.lineFeed(); + 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)); + } + this._terminal.error('Unknown EXEC flag: %s.', flag); + } + + inst_c(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': + 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); + } + + inst_e(collected: string, flag: string): void { + let cs; + + switch (collected) { + case '': + switch (flag) { + // case '6': // Back Index (DECBI), VT420 and up - not supported + case '7': // Save Cursor (DECSC) + this._inputHandler.saveCursor(); + return; + case '8': // Restore Cursor (DECRC) + this._inputHandler.restoreCursor(); + return; + // case '9': // Forward Index (DECFI), VT420 and up - not supported + case 'D': // Index (IND is 0x84) + this._terminal.index(); + return; + 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) + (this._terminal).tabSet(); + return; + case 'M': // Reverse Index (RI is 0x8d) + this._terminal.reverseIndex(); + return; + 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). + this._terminal.setgLevel(2); + return; + case 'o': // Invoke the G3 Character Set as GL (LS3). + this._terminal.setgLevel(3); + return; + case '|': // Invoke the G3 Character Set as GR (LS3R). + this._terminal.setgLevel(3); + return; + case '}': // Invoke the G2 Character Set as GR (LS2R). + this._terminal.setgLevel(2); + return; + case '~': // Invoke the G1 Character Set as GR (LS1R). + this._terminal.setgLevel(1); + return; + } + // 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) + cs = CHARSETS[flag]; + if (!cs) cs = DEFAULT_CHARSET; + this._terminal.setgCharset(0, cs); + return; + case ')': // G1 (VT100) + cs = CHARSETS[flag]; + if (!cs) cs = DEFAULT_CHARSET; + this._terminal.setgCharset(1, cs); + return; + case '*': // G2 (VT220) + cs = CHARSETS[flag]; + if (!cs) cs = DEFAULT_CHARSET; + this._terminal.setgCharset(2, cs); + return; + case '+': // G3 (VT220) + cs = CHARSETS[flag]; + if (!cs) cs = DEFAULT_CHARSET; + this._terminal.setgCharset(3, cs); + return; + case '-': // G1 (VT300) + cs = CHARSETS[flag]; + if (!cs) cs = DEFAULT_CHARSET; + this._terminal.setgCharset(1, cs); + return; + case '.': // G2 (VT300) + if (!cs) cs = DEFAULT_CHARSET; + this._terminal.setgCharset(2, cs); + return; + case '/': // G3 (VT300) + // not supported - how to deal with this? (original code is not reachable) + return; + default: + this._terminal.error('Unknown ESC control: %s %s.', collected, flag); + } + } + + inst_H(collected: string, params: number[], flag: string): void { + // TODO + } + + inst_P(dcs: string): void { + // TODO + } + + inst_U(): void { + // TODO + } + + inst_E(): void { + // TODO + } +} diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 4df3e69550..9e7ad3a51a 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -22,7 +22,7 @@ export class InputHandler implements IInputHandler { constructor(private _terminal: IInputHandlingTerminal) { } public addChar(char: string, code: number): void { - if (char >= ' ') { + // if (char >= ' ') { // make buffer local for faster access const buffer = this._terminal.buffer; @@ -111,7 +111,7 @@ export class InputHandler implements IInputHandler { buffer.lines.get(row)[buffer.x] = [this._terminal.curAttr, '', 0, undefined]; buffer.x++; } - } + // } } /** diff --git a/src/Terminal.ts b/src/Terminal.ts index ba1fff3df1..44c9064333 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -32,7 +32,7 @@ import { Viewport } from './Viewport'; import { rightClickHandler, moveTextAreaUnderMouseCursor, pasteHandler, copyHandler } from './handlers/Clipboard'; import { C0 } from './EscapeSequences'; import { InputHandler } from './InputHandler'; -import { Parser } from './Parser'; +// import { Parser } from './Parser'; import { Renderer } from './renderer/Renderer'; import { Linkifier } from './Linkifier'; import { SelectionManager } from './SelectionManager'; @@ -49,6 +49,7 @@ import { AccessibilityManager } from './AccessibilityManager'; import { ScreenDprMonitor } from './utils/ScreenDprMonitor'; import { ITheme, ILocalizableStrings, IMarker, IDisposable } from 'xterm'; import { removeTerminalFromCache } from './renderer/atlas/CharAtlas'; +import { ParserTerminal } from './EscapeSequenceParser'; // reg + shift key mappings for digits and special chars const KEYCODE_KEY_MAPPINGS = { @@ -216,7 +217,8 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II private _inputHandler: InputHandler; public soundManager: SoundManager; - private _parser: Parser; + // private _parser: Parser; + private _newParser: ParserTerminal; public renderer: IRenderer; public selectionManager: SelectionManager; public linkifier: ILinkifier; @@ -331,7 +333,8 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this._userScrolling = false; this._inputHandler = new InputHandler(this); - this._parser = new Parser(this._inputHandler, this); + // this._parser = new Parser(this._inputHandler, this); + this._newParser = new ParserTerminal(this, this._inputHandler); // Reuse renderer if the Terminal is being recreated via a reset call. this.renderer = this.renderer || null; this.selectionManager = this.selectionManager || null; @@ -1315,8 +1318,10 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // middle of parsing escape sequence in two chunks. For some reason the // state of the parser resets to 0 after exiting parser.parse. This change // just sets the state back based on the correct return statement. - const state = this._parser.parse(data); - this._parser.setState(state); + + // const state = this._parser.parse(data); + this._newParser.write(data); + // this._parser.setState(state); this.updateRange(this.buffer.y); this.refresh(this._refreshStart, this._refreshEnd); From c8910cc5fe27f5186b798f2bc88bd5d35fd02366 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 22 Apr 2018 04:34:42 +0200 Subject: [PATCH 05/44] utilize more typescript features --- src/EscapeSequenceParser.test.ts | 1817 +++++++++++++++--------------- src/EscapeSequenceParser.ts | 577 +++++----- src/Terminal.ts | 4 +- 3 files changed, 1243 insertions(+), 1155 deletions(-) diff --git a/src/EscapeSequenceParser.test.ts b/src/EscapeSequenceParser.test.ts index eefa760356..296ac34d4f 100644 --- a/src/EscapeSequenceParser.test.ts +++ b/src/EscapeSequenceParser.test.ts @@ -1,4 +1,4 @@ -import { AnsiParser, IParserTerminal } from './EscapeSequenceParser'; +import { EscapeSequenceParser, IParserTerminal, STATE } from './EscapeSequenceParser'; import * as chai from 'chai'; function r(a: number, b: number): string[] { @@ -24,1019 +24,1040 @@ let testTerminal: ITestTerminal = { compare: function (value: any): void { chai.expect(this.calls.slice()).eql(value); // weird bug w'o slicing here }, - inst_p: function (s: string, start: number, end: number): void { - this.calls.push(['print', s.substring(start, end)]); + actionPrint: function (data: string, start: number, end: number): void { + this.calls.push(['print', data.substring(start, end)]); }, - inst_o: function (s: string): void { + actionOSC: function (s: string): void { this.calls.push(['osc', s]); }, - inst_x: function (flag: string): void { + actionExecute: function (flag: string): void { this.calls.push(['exe', flag]); }, - inst_c: function (collected: string, params: number[], flag: string): void { + actionCSI: function (collected: string, params: number[], flag: string): void { this.calls.push(['csi', collected, params, flag]); }, - inst_e: function (collected: string, flag: string): void { + actionESC: function (collected: string, flag: string): void { this.calls.push(['esc', collected, flag]); }, - inst_H: function (collected: string, params: number[], flag: string): void { + actionDCSHook: function (collected: string, params: number[], flag: string): void { this.calls.push(['dcs hook', collected, params, flag]); }, - inst_P: function (dcs: string): void { - this.calls.push(['dcs put', dcs]); + actionDCSPrint: function (data: string, start: number, end: number): void { + this.calls.push(['dcs put', data.substring(start, end)]); }, - inst_U: function (): void { + actionDCSUnhook: function (): void { this.calls.push(['dcs unhook']); } }; -let parser = new AnsiParser(testTerminal); +let states: number[] = [ + STATE.GROUND, + STATE.ESCAPE, + STATE.ESCAPE_INTERMEDIATE, + STATE.CSI_ENTRY, + STATE.CSI_PARAM, + STATE.CSI_INTERMEDIATE, + STATE.CSI_IGNORE, + STATE.SOS_PM_APC_STRING, + STATE.OSC_STRING, + STATE.DCS_ENTRY, + STATE.DCS_PARAM, + STATE.DCS_IGNORE, + STATE.DCS_INTERMEDIATE, + STATE.DCS_PASSTHROUGH +]; +let state: any; -describe('Parser init and methods', function(): void { - it('parser init', function (): void { - let p = new AnsiParser({}); - chai.expect(p.term).a('object'); - chai.expect(p.term.inst_p).a('function'); - chai.expect(p.term.inst_o).a('function'); - chai.expect(p.term.inst_x).a('function'); - chai.expect(p.term.inst_c).a('function'); - chai.expect(p.term.inst_e).a('function'); - chai.expect(p.term.inst_H).a('function'); - chai.expect(p.term.inst_P).a('function'); - chai.expect(p.term.inst_U).a('function'); - p.parse('\x1b[31mHello World!'); - }); - it('terminal callbacks', function (): void { - chai.expect(parser.term).equal(testTerminal); - chai.expect(parser.term.inst_p).equal(testTerminal.inst_p); - chai.expect(parser.term.inst_o).equal(testTerminal.inst_o); - chai.expect(parser.term.inst_x).equal(testTerminal.inst_x); - chai.expect(parser.term.inst_c).equal(testTerminal.inst_c); - chai.expect(parser.term.inst_e).equal(testTerminal.inst_e); - chai.expect(parser.term.inst_H).equal(testTerminal.inst_H); - chai.expect(parser.term.inst_P).equal(testTerminal.inst_P); - chai.expect(parser.term.inst_U).equal(testTerminal.inst_U); - }); - 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 = '#'; +let parser = new EscapeSequenceParser(testTerminal); - parser.reset(); - chai.expect(parser.currentState).equal(0); - chai.expect(parser.osc).equal(''); - chai.expect(parser.params).eql([0]); - chai.expect(parser.collected).equal(''); - }); -}); +describe('EscapeSequenceParser', function(): void { -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 = 0; - parser.parse(exes[i]); + 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.actionPrint); + 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); - testTerminal.compare([['exe', exes[i]]]); + 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(); - testTerminal.clear(); - } + chai.expect(parser.currentState).equal(STATE.GROUND); + chai.expect(parser.osc).equal(''); + chai.expect(parser.params).eql([0]); + chai.expect(parser.collected).equal(''); + }); }); - 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 = 0; - parser.parse(printables[i]); - chai.expect(parser.currentState).equal(0); - testTerminal.compare([['print', printables[i]]]); + + describe('state transitions and actions', function(): void { + it('state GROUND execute action', function (): void { 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 (let state = 0; state < 14; ++state) { + let exes = r(0x00, 0x18); + exes.concat(['\x19']); + exes.concat(r(0x1c, 0x20)); for (let i = 0; i < exes.length; ++i) { + parser.currentState = STATE.GROUND; + parser.parse(exes[i]); + chai.expect(parser.currentState).equal(STATE.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 = STATE.GROUND; + parser.parse(printables[i]); + chai.expect(parser.currentState).equal(STATE.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(STATE.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(STATE.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(STATE.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 = STATE.ESCAPE; parser.parse(exes[i]); - chai.expect(parser.currentState).equal(0); - testTerminal.compare(((exceptions[state]) ? exceptions[state][exes[i]] : 0) || [['exe', exes[i]]]); + chai.expect(parser.currentState).equal(STATE.ESCAPE); + testTerminal.compare([['exe', exes[i]]]); parser.reset(); testTerminal.clear(); } - parser.parse('\x9c'); - chai.expect(parser.currentState).equal(0); - testTerminal.compare([]); + }); + it('state ESCAPE ignore', function (): void { parser.reset(); testTerminal.clear(); - } - }); - it('trans ANYWHERE --> ESCAPE with clear', function (): void { - parser.reset(); - for (let state = 0; state < 14; ++state) { - parser.currentState = state; - parser.osc = '#'; - parser.params = [23]; - parser.collected = '#'; - parser.parse('\x1b'); - chai.expect(parser.currentState).equal(1); - chai.expect(parser.osc).equal(''); - chai.expect(parser.params).eql([0]); - chai.expect(parser.collected).equal(''); + parser.currentState = STATE.ESCAPE; + parser.parse('\x7f'); + chai.expect(parser.currentState).equal(STATE.ESCAPE); + testTerminal.compare([]); 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 = 1; - parser.parse(exes[i]); - chai.expect(parser.currentState).equal(1); - testTerminal.compare([['exe', exes[i]]]); + testTerminal.clear(); + }); + it('trans ESCAPE --> GROUND with ecs_dispatch action', function (): void { parser.reset(); testTerminal.clear(); - } - }); - it('state ESCAPE ignore', function (): void { - parser.reset(); - testTerminal.clear(); - parser.currentState = 1; - parser.parse('\x7f'); - chai.expect(parser.currentState).equal(1); - 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 = 1; - parser.parse(dispatches[i]); - chai.expect(parser.currentState).equal(0); - testTerminal.compare([['esc', '', dispatches[i]]]); + 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 = STATE.ESCAPE; + parser.parse(dispatches[i]); + chai.expect(parser.currentState).equal(STATE.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 = STATE.ESCAPE; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(STATE.ESCAPE_INTERMEDIATE); + chai.expect(parser.collected).equal(collect[i]); + parser.reset(); + } + }); + it('state ESCAPE_INTERMEDIATE execute rules', function (): void { 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 = 1; - parser.parse(collect[i]); - chai.expect(parser.currentState).equal(2); - chai.expect(parser.collected).equal(collect[i]); + let exes = r(0x00, 0x18); + exes.concat(['\x19']); + exes.concat(r(0x1c, 0x20)); + for (let i = 0; i < exes.length; ++i) { + parser.currentState = STATE.ESCAPE_INTERMEDIATE; + parser.parse(exes[i]); + chai.expect(parser.currentState).equal(STATE.ESCAPE_INTERMEDIATE); + testTerminal.compare([['exe', exes[i]]]); + parser.reset(); + testTerminal.clear(); + } + }); + it('state ESCAPE_INTERMEDIATE ignore', function (): void { 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 = 2; - parser.parse(exes[i]); - chai.expect(parser.currentState).equal(2); - testTerminal.compare([['exe', exes[i]]]); + testTerminal.clear(); + parser.currentState = STATE.ESCAPE_INTERMEDIATE; + parser.parse('\x7f'); + chai.expect(parser.currentState).equal(STATE.ESCAPE_INTERMEDIATE); + testTerminal.compare([]); parser.reset(); testTerminal.clear(); - } - }); - it('state ESCAPE_INTERMEDIATE ignore', function (): void { - parser.reset(); - testTerminal.clear(); - parser.currentState = 2; - parser.parse('\x7f'); - chai.expect(parser.currentState).equal(2); - 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 = 2; - parser.parse(collect[i]); - chai.expect(parser.currentState).equal(2); - chai.expect(parser.collected).equal(collect[i]); + }); + it('state ESCAPE_INTERMEDIATE collect action', function (): void { 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 = 2; - parser.parse(collect[i]); - chai.expect(parser.currentState).equal(0); - testTerminal.compare([['esc', '', collect[i]]]); + let collect = r(0x20, 0x30); + for (let i = 0; i < collect.length; ++i) { + parser.currentState = STATE.ESCAPE_INTERMEDIATE; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(STATE.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(); - } - }); - it('trans ANYWHERE/ESCAPE --> CSI_ENTRY with clear', function (): void { - parser.reset(); - // C0 - parser.currentState = 1; - parser.osc = '#'; - parser.params = [123]; - parser.collected = '#'; - parser.parse('['); - chai.expect(parser.currentState).equal(3); - chai.expect(parser.osc).equal(''); - chai.expect(parser.params).eql([0]); - chai.expect(parser.collected).equal(''); - parser.reset(); - // C1 - for (let state = 0; state < 14; ++state) { - parser.currentState = state; + let collect = r(0x30, 0x7f); + for (let i = 0; i < collect.length; ++i) { + parser.currentState = STATE.ESCAPE_INTERMEDIATE; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(STATE.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 = STATE.ESCAPE; parser.osc = '#'; parser.params = [123]; parser.collected = '#'; - parser.parse('\x9b'); - chai.expect(parser.currentState).equal(3); + parser.parse('['); + chai.expect(parser.currentState).equal(STATE.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 = 3; - parser.parse(exes[i]); - chai.expect(parser.currentState).equal(3); - testTerminal.compare([['exe', exes[i]]]); + // C1 + for (state in states) { + parser.currentState = state; + parser.osc = '#'; + parser.params = [123]; + parser.collected = '#'; + parser.parse('\x9b'); + chai.expect(parser.currentState).equal(STATE.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(); - } - }); - it('state CSI_ENTRY ignore', function (): void { - parser.reset(); - testTerminal.clear(); - parser.currentState = 3; - parser.parse('\x7f'); - chai.expect(parser.currentState).equal(3); - 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 = 3; - parser.parse(dispatches[i]); - chai.expect(parser.currentState).equal(0); - testTerminal.compare([['csi', '', [0], dispatches[i]]]); + let exes = r(0x00, 0x18); + exes.concat(['\x19']); + exes.concat(r(0x1c, 0x20)); + for (let i = 0; i < exes.length; ++i) { + parser.currentState = STATE.CSI_ENTRY; + parser.parse(exes[i]); + chai.expect(parser.currentState).equal(STATE.CSI_ENTRY); + testTerminal.compare([['exe', exes[i]]]); + parser.reset(); + testTerminal.clear(); + } + }); + it('state CSI_ENTRY ignore', function (): void { 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 = 3; - parser.parse(params[i]); - chai.expect(parser.currentState).equal(4); - chai.expect(parser.params).eql([params[i].charCodeAt(0) - 48]); + parser.currentState = STATE.CSI_ENTRY; + parser.parse('\x7f'); + chai.expect(parser.currentState).equal(STATE.CSI_ENTRY); + testTerminal.compare([]); parser.reset(); - } - // ';' - parser.currentState = 3; - parser.parse('\x3b'); - chai.expect(parser.currentState).equal(4); - chai.expect(parser.params).eql([0, 0]); - parser.reset(); - for (let i = 0; i < collect.length; ++i) { - parser.currentState = 3; - parser.parse(collect[i]); - chai.expect(parser.currentState).equal(4); - chai.expect(parser.collected).equal(collect[i]); + testTerminal.clear(); + }); + it('trans CSI_ENTRY --> GROUND with csi_dispatch action', function (): void { 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 = 4; - parser.parse(exes[i]); - chai.expect(parser.currentState).equal(4); - testTerminal.compare([['exe', exes[i]]]); + let dispatches = r(0x40, 0x7f); + for (let i = 0; i < dispatches.length; ++i) { + parser.currentState = STATE.CSI_ENTRY; + parser.parse(dispatches[i]); + chai.expect(parser.currentState).equal(STATE.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(); - 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 = 4; - parser.parse(params[i]); - chai.expect(parser.currentState).equal(4); - chai.expect(parser.params).eql([params[i].charCodeAt(0) - 48]); + 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 = STATE.CSI_ENTRY; + parser.parse(params[i]); + chai.expect(parser.currentState).equal(STATE.CSI_PARAM); + chai.expect(parser.params).eql([params[i].charCodeAt(0) - 48]); + parser.reset(); + } + parser.currentState = STATE.CSI_ENTRY; + parser.parse('\x3b'); + chai.expect(parser.currentState).equal(STATE.CSI_PARAM); + chai.expect(parser.params).eql([0, 0]); parser.reset(); - } - parser.currentState = 4; - parser.parse('\x3b'); - chai.expect(parser.currentState).equal(4); - chai.expect(parser.params).eql([0, 0]); - parser.reset(); - }); - it('state CSI_PARAM ignore', function (): void { - parser.reset(); - testTerminal.clear(); - parser.currentState = 4; - parser.parse('\x7f'); - chai.expect(parser.currentState).equal(4); - 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 = 4; - parser.params = [0, 1]; - parser.parse(dispatches[i]); - chai.expect(parser.currentState).equal(0); - testTerminal.compare([['csi', '', [0, 1], dispatches[i]]]); + for (let i = 0; i < collect.length; ++i) { + parser.currentState = STATE.CSI_ENTRY; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(STATE.CSI_PARAM); + chai.expect(parser.collected).equal(collect[i]); + parser.reset(); + } + }); + it('state CSI_PARAM execute rules', function (): void { 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 = 3; - parser.parse(collect[i]); - chai.expect(parser.currentState).equal(5); - chai.expect(parser.collected).equal(collect[i]); + let exes = r(0x00, 0x18); + exes.concat(['\x19']); + exes.concat(r(0x1c, 0x20)); + for (let i = 0; i < exes.length; ++i) { + parser.currentState = STATE.CSI_PARAM; + parser.parse(exes[i]); + chai.expect(parser.currentState).equal(STATE.CSI_PARAM); + testTerminal.compare([['exe', exes[i]]]); + parser.reset(); + testTerminal.clear(); + } + }); + it('state CSI_PARAM param action', function (): void { 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 = 4; - parser.parse(collect[i]); - chai.expect(parser.currentState).equal(5); - chai.expect(parser.collected).equal(collect[i]); + let params = ['\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', '\x38', '\x39']; + for (let i = 0; i < params.length; ++i) { + parser.currentState = STATE.CSI_PARAM; + parser.parse(params[i]); + chai.expect(parser.currentState).equal(STATE.CSI_PARAM); + chai.expect(parser.params).eql([params[i].charCodeAt(0) - 48]); + parser.reset(); + } + parser.currentState = STATE.CSI_PARAM; + parser.parse('\x3b'); + chai.expect(parser.currentState).equal(STATE.CSI_PARAM); + chai.expect(parser.params).eql([0, 0]); 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 = 5; - parser.parse(exes[i]); - chai.expect(parser.currentState).equal(5); - testTerminal.compare([['exe', exes[i]]]); + }); + it('state CSI_PARAM ignore', function (): void { 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 = 5; - parser.parse(collect[i]); - chai.expect(parser.currentState).equal(5); - chai.expect(parser.collected).equal(collect[i]); - parser.reset(); - } - }); - it('state CSI_INTERMEDIATE ignore', function (): void { - parser.reset(); - testTerminal.clear(); - parser.currentState = 5; - parser.parse('\x7f'); - chai.expect(parser.currentState).equal(5); - 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 = 5; - parser.params = [0, 1]; - parser.parse(dispatches[i]); - chai.expect(parser.currentState).equal(0); - testTerminal.compare([['csi', '', [0, 1], dispatches[i]]]); + parser.currentState = STATE.CSI_PARAM; + parser.parse('\x7f'); + chai.expect(parser.currentState).equal(STATE.CSI_PARAM); + testTerminal.compare([]); parser.reset(); testTerminal.clear(); - } - }); - it('trans CSI_ENTRY --> CSI_IGNORE', function (): void { - parser.reset(); - parser.currentState = 3; - parser.parse('\x3a'); - chai.expect(parser.currentState).equal(6); - 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 = 4; - parser.parse('\x3b' + chars[i]); - chai.expect(parser.currentState).equal(6); - chai.expect(parser.params).eql([0, 0]); + }); + it('trans CSI_PARAM --> GROUND with csi_dispatch action', function (): void { 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 = 5; - parser.parse(chars[i]); - chai.expect(parser.currentState).equal(6); - chai.expect(parser.params).eql([0]); + let dispatches = r(0x40, 0x7f); + for (let i = 0; i < dispatches.length; ++i) { + parser.currentState = STATE.CSI_PARAM; + parser.params = [0, 1]; + parser.parse(dispatches[i]); + chai.expect(parser.currentState).equal(STATE.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(); - } - }); - 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 = 6; - parser.parse(exes[i]); - chai.expect(parser.currentState).equal(6); - testTerminal.compare([['exe', exes[i]]]); + let collect = r(0x20, 0x30); + for (let i = 0; i < collect.length; ++i) { + parser.currentState = STATE.CSI_ENTRY; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(STATE.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 = STATE.CSI_PARAM; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(STATE.CSI_INTERMEDIATE); + chai.expect(parser.collected).equal(collect[i]); + parser.reset(); + } + }); + it('state CSI_INTERMEDIATE execute rules', function (): void { 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 = 6; - parser.parse(ignored[i]); - chai.expect(parser.currentState).equal(6); - testTerminal.compare([]); + let exes = r(0x00, 0x18); + exes.concat(['\x19']); + exes.concat(r(0x1c, 0x20)); + for (let i = 0; i < exes.length; ++i) { + parser.currentState = STATE.CSI_INTERMEDIATE; + parser.parse(exes[i]); + chai.expect(parser.currentState).equal(STATE.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 = STATE.CSI_INTERMEDIATE; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(STATE.CSI_INTERMEDIATE); + chai.expect(parser.collected).equal(collect[i]); + parser.reset(); + } + }); + it('state CSI_INTERMEDIATE ignore', function (): void { 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 = 6; - parser.params = [0, 1]; - parser.parse(dispatches[i]); - chai.expect(parser.currentState).equal(0); + parser.currentState = STATE.CSI_INTERMEDIATE; + parser.parse('\x7f'); + chai.expect(parser.currentState).equal(STATE.CSI_INTERMEDIATE); 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(7); + }); + it('trans CSI_INTERMEDIATE --> GROUND with csi_dispatch action', function (): void { parser.reset(); - } - // C1 - for (let state = 0; state < 14; ++state) { - parser.currentState = state; - initializers = ['\x98', '\x9e', '\x9f']; + let dispatches = r(0x40, 0x7f); + for (let i = 0; i < dispatches.length; ++i) { + parser.currentState = STATE.CSI_INTERMEDIATE; + parser.params = [0, 1]; + parser.parse(dispatches[i]); + chai.expect(parser.currentState).equal(STATE.GROUND); + testTerminal.compare([['csi', '', [0, 1], dispatches[i]]]); + parser.reset(); + testTerminal.clear(); + } + }); + it('trans CSI_ENTRY --> CSI_IGNORE', function (): void { + parser.reset(); + parser.currentState = STATE.CSI_ENTRY; + parser.parse('\x3a'); + chai.expect(parser.currentState).equal(STATE.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 = STATE.CSI_PARAM; + parser.parse('\x3b' + chars[i]); + chai.expect(parser.currentState).equal(STATE.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 = STATE.CSI_INTERMEDIATE; + parser.parse(chars[i]); + chai.expect(parser.currentState).equal(STATE.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 = STATE.CSI_IGNORE; + parser.parse(exes[i]); + chai.expect(parser.currentState).equal(STATE.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 = STATE.CSI_IGNORE; + parser.parse(ignored[i]); + chai.expect(parser.currentState).equal(STATE.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 = STATE.CSI_IGNORE; + parser.params = [0, 1]; + parser.parse(dispatches[i]); + chai.expect(parser.currentState).equal(STATE.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(initializers[i]); - chai.expect(parser.currentState).equal(7); + parser.parse('\x1b' + initializers[i]); + chai.expect(parser.currentState).equal(STATE.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 = 7; - parser.parse(ignored[i]); - chai.expect(parser.currentState).equal(7); + // 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(STATE.SOS_PM_APC_STRING); + parser.reset(); + } + } + }); + it('state SOS_PM_APC_STRING ignore rules', function (): void { parser.reset(); - } - }); - it('trans ANYWHERE/ESCAPE --> OSC_STRING', function (): void { - parser.reset(); - // C0 - parser.parse('\x1b]'); - chai.expect(parser.currentState).equal(8); - parser.reset(); - // C1 - for (let state = 0; state < 14; ++state) { - parser.currentState = state; - parser.parse('\x9d'); - chai.expect(parser.currentState).equal(8); + 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 = STATE.SOS_PM_APC_STRING; + parser.parse(ignored[i]); + chai.expect(parser.currentState).equal(STATE.SOS_PM_APC_STRING); + parser.reset(); + } + }); + it('trans ANYWHERE/ESCAPE --> OSC_STRING', function (): void { 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 = 8; - parser.parse(ignored[i]); - chai.expect(parser.currentState).equal(8); - chai.expect(parser.osc).equal(''); + // C0 + parser.parse('\x1b]'); + chai.expect(parser.currentState).equal(STATE.OSC_STRING); 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 = 8; - parser.parse(puts[i]); - chai.expect(parser.currentState).equal(8); - chai.expect(parser.osc).equal(puts[i]); + // C1 + for (state in states) { + parser.currentState = state; + parser.parse('\x9d'); + chai.expect(parser.currentState).equal(STATE.OSC_STRING); + parser.reset(); + } + }); + it('state OSC_STRING ignore rules', function (): void { parser.reset(); - } - }); - it('state DCS_ENTRY', function (): void { - parser.reset(); - // C0 - parser.parse('\x1bP'); - chai.expect(parser.currentState).equal(9); - parser.reset(); - // C1 - for (let state = 0; state < 14; ++state) { - parser.currentState = state; - parser.parse('\x90'); - chai.expect(parser.currentState).equal(9); + 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 = STATE.OSC_STRING; + parser.parse(ignored[i]); + chai.expect(parser.currentState).equal(STATE.OSC_STRING); + chai.expect(parser.osc).equal(''); + parser.reset(); + } + }); + it('state OSC_STRING put action', function (): void { 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 = 9; - parser.parse(ignored[i]); - chai.expect(parser.currentState).equal(9); + let puts = r(0x20, 0x80); + for (let i = 0; i < puts.length; ++i) { + parser.currentState = STATE.OSC_STRING; + parser.parse(puts[i]); + chai.expect(parser.currentState).equal(STATE.OSC_STRING); + chai.expect(parser.osc).equal(puts[i]); + parser.reset(); + } + }); + it('state DCS_ENTRY', function (): void { 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 = 9; - parser.parse(params[i]); - chai.expect(parser.currentState).equal(10); - chai.expect(parser.params).eql([params[i].charCodeAt(0) - 48]); + // C0 + parser.parse('\x1bP'); + chai.expect(parser.currentState).equal(STATE.DCS_ENTRY); parser.reset(); - } - parser.currentState = 9; - parser.parse('\x3b'); - chai.expect(parser.currentState).equal(10); - chai.expect(parser.params).eql([0, 0]); - parser.reset(); - for (let i = 0; i < collect.length; ++i) { - parser.currentState = 9; - parser.parse(collect[i]); - chai.expect(parser.currentState).equal(10); - chai.expect(parser.collected).equal(collect[i]); + // C1 + for (state in states) { + parser.currentState = state; + parser.parse('\x90'); + chai.expect(parser.currentState).equal(STATE.DCS_ENTRY); + parser.reset(); + } + }); + it('state DCS_ENTRY ignore rules', function (): void { 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 = 10; - parser.parse(ignored[i]); - chai.expect(parser.currentState).equal(10); + 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 = STATE.DCS_ENTRY; + parser.parse(ignored[i]); + chai.expect(parser.currentState).equal(STATE.DCS_ENTRY); + parser.reset(); + } + }); + it('state DCS_ENTRY --> DCS_PARAM with param/collect actions', function (): void { 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 = 10; - parser.parse(params[i]); - chai.expect(parser.currentState).equal(10); - chai.expect(parser.params).eql([params[i].charCodeAt(0) - 48]); + 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 = STATE.DCS_ENTRY; + parser.parse(params[i]); + chai.expect(parser.currentState).equal(STATE.DCS_PARAM); + chai.expect(parser.params).eql([params[i].charCodeAt(0) - 48]); + parser.reset(); + } + parser.currentState = STATE.DCS_ENTRY; + parser.parse('\x3b'); + chai.expect(parser.currentState).equal(STATE.DCS_PARAM); + chai.expect(parser.params).eql([0, 0]); parser.reset(); - } - parser.currentState = 10; - parser.parse('\x3b'); - chai.expect(parser.currentState).equal(10); - chai.expect(parser.params).eql([0, 0]); - parser.reset(); - }); - it('trans DCS_ENTRY --> DCS_IGNORE', function (): void { - parser.reset(); - parser.currentState = 9; - parser.parse('\x3a'); - chai.expect(parser.currentState).equal(11); - 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 = 10; - parser.parse('\x3b' + chars[i]); - chai.expect(parser.currentState).equal(11); + for (let i = 0; i < collect.length; ++i) { + parser.currentState = STATE.DCS_ENTRY; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(STATE.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 = STATE.DCS_PARAM; + parser.parse(ignored[i]); + chai.expect(parser.currentState).equal(STATE.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 = STATE.DCS_PARAM; + parser.parse(params[i]); + chai.expect(parser.currentState).equal(STATE.DCS_PARAM); + chai.expect(parser.params).eql([params[i].charCodeAt(0) - 48]); + parser.reset(); + } + parser.currentState = STATE.DCS_PARAM; + parser.parse('\x3b'); + chai.expect(parser.currentState).equal(STATE.DCS_PARAM); 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 = 12; - parser.parse(chars[i]); - chai.expect(parser.currentState).equal(11); + }); + it('trans DCS_ENTRY --> DCS_IGNORE', function (): void { 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 = 11; - parser.parse(ignored[i]); - chai.expect(parser.currentState).equal(11); + parser.currentState = STATE.DCS_ENTRY; + parser.parse('\x3a'); + chai.expect(parser.currentState).equal(STATE.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 = 9; - parser.parse(collect[i]); - chai.expect(parser.currentState).equal(12); - chai.expect(parser.collected).equal(collect[i]); + }); + it('trans DCS_PARAM --> DCS_IGNORE', function (): void { 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 = 10; - parser.parse(collect[i]); - chai.expect(parser.currentState).equal(12); - chai.expect(parser.collected).equal(collect[i]); + let chars = ['\x3a', '\x3c', '\x3d', '\x3e', '\x3f']; + for (let i = 0; i < chars.length; ++i) { + parser.currentState = STATE.DCS_PARAM; + parser.parse('\x3b' + chars[i]); + chai.expect(parser.currentState).equal(STATE.DCS_IGNORE); + chai.expect(parser.params).eql([0, 0]); + parser.reset(); + } + }); + it('trans DCS_INTERMEDIATE --> DCS_IGNORE', function (): void { 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 = 12; - parser.parse(ignored[i]); - chai.expect(parser.currentState).equal(12); + let chars = r(0x30, 0x40); + for (let i = 0; i < chars.length; ++i) { + parser.currentState = STATE.DCS_INTERMEDIATE; + parser.parse(chars[i]); + chai.expect(parser.currentState).equal(STATE.DCS_IGNORE); + parser.reset(); + } + }); + it('state DCS_IGNORE ignore rules', function (): void { 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 = 12; - parser.parse(collect[i]); - chai.expect(parser.currentState).equal(12); - chai.expect(parser.collected).equal(collect[i]); + 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 = STATE.DCS_IGNORE; + parser.parse(ignored[i]); + chai.expect(parser.currentState).equal(STATE.DCS_IGNORE); + parser.reset(); + } + }); + it('trans DCS_ENTRY --> DCS_INTERMEDIATE with collect action', function (): void { 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 = 12; - parser.parse('\x20' + chars[i]); - chai.expect(parser.currentState).equal(11); - chai.expect(parser.collected).equal('\x20'); + let collect = r(0x20, 0x30); + for (let i = 0; i < collect.length; ++i) { + parser.currentState = STATE.DCS_ENTRY; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(STATE.DCS_INTERMEDIATE); + chai.expect(parser.collected).equal(collect[i]); + parser.reset(); + } + }); + it('trans DCS_PARAM --> DCS_INTERMEDIATE with collect action', function (): void { 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 = 9; - parser.parse(collect[i]); - chai.expect(parser.currentState).equal(13); - testTerminal.compare([['dcs hook', '', [0], collect[i]]]); + let collect = r(0x20, 0x30); + for (let i = 0; i < collect.length; ++i) { + parser.currentState = STATE.DCS_PARAM; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(STATE.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 = STATE.DCS_INTERMEDIATE; + parser.parse(ignored[i]); + chai.expect(parser.currentState).equal(STATE.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 = STATE.DCS_INTERMEDIATE; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(STATE.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 = STATE.DCS_INTERMEDIATE; + parser.parse('\x20' + chars[i]); + chai.expect(parser.currentState).equal(STATE.DCS_IGNORE); + chai.expect(parser.collected).equal('\x20'); + parser.reset(); + } + }); + it('trans DCS_ENTRY --> DCS_PASSTHROUGH with hook', function (): void { 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 = 10; - parser.parse(collect[i]); - chai.expect(parser.currentState).equal(13); - testTerminal.compare([['dcs hook', '', [0], collect[i]]]); + let collect = r(0x40, 0x7f); + for (let i = 0; i < collect.length; ++i) { + parser.currentState = STATE.DCS_ENTRY; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(STATE.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(); - } - }); - 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 = 12; - parser.parse(collect[i]); - chai.expect(parser.currentState).equal(13); - testTerminal.compare([['dcs hook', '', [0], collect[i]]]); + let collect = r(0x40, 0x7f); + for (let i = 0; i < collect.length; ++i) { + parser.currentState = STATE.DCS_PARAM; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(STATE.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(); - } - }); - 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 = 13; - parser.parse(puts[i]); - chai.expect(parser.currentState).equal(13); - testTerminal.compare([['dcs put', puts[i]]]); + let collect = r(0x40, 0x7f); + for (let i = 0; i < collect.length; ++i) { + parser.currentState = STATE.DCS_INTERMEDIATE; + parser.parse(collect[i]); + chai.expect(parser.currentState).equal(STATE.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(); - } - }); - it('state DCS_PASSTHROUGH ignore', function (): void { - parser.reset(); - testTerminal.clear(); - parser.currentState = 13; - parser.parse('\x7f'); - chai.expect(parser.currentState).equal(13); - testTerminal.compare([]); - 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 = STATE.DCS_PASSTHROUGH; + parser.parse(puts[i]); + chai.expect(parser.currentState).equal(STATE.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 = STATE.DCS_PASSTHROUGH; + parser.parse('\x7f'); + chai.expect(parser.currentState).equal(STATE.DCS_PASSTHROUGH); + testTerminal.compare([]); + parser.reset(); + testTerminal.clear(); + }); }); -}); -function test(s: string, value: any, noReset: any): void { - if (!noReset) { - 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); } - 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'] + 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); + }); }); - 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 = 6; - parser.parse('€öäü'); - chai.expect(parser.currentState).equal(6); - testTerminal.compare([]); - parser.reset(); - testTerminal.clear(); - }); - it('DCS_IGNORE error', function (): void { - parser.reset(); - testTerminal.clear(); - parser.currentState = 11; - parser.parse('€öäü'); - chai.expect(parser.currentState).equal(11); - testTerminal.compare([]); - parser.reset(); - testTerminal.clear(); - }); - it('DCS_PASSTHROUGH error', function (): void { - parser.reset(); - testTerminal.clear(); - parser.currentState = 13; - parser.parse('€öäü'); - chai.expect(parser.currentState).equal(13); - testTerminal.compare([['dcs put', '€öäü']]); - parser.reset(); - testTerminal.clear(); - }); - it('error else of if (code > 159)', function (): void { - parser.reset(); - testTerminal.clear(); - parser.currentState = 0; - parser.parse('\x1e'); - chai.expect(parser.currentState).equal(0); - testTerminal.compare([]); - parser.reset(); - testTerminal.clear(); + describe('coverage tests', function(): void { + it('CSI_IGNORE error', function (): void { + parser.reset(); + testTerminal.clear(); + parser.currentState = STATE.CSI_IGNORE; + parser.parse('€öäü'); + chai.expect(parser.currentState).equal(STATE.CSI_IGNORE); + testTerminal.compare([]); + parser.reset(); + testTerminal.clear(); + }); + it('DCS_IGNORE error', function (): void { + parser.reset(); + testTerminal.clear(); + parser.currentState = STATE.DCS_IGNORE; + parser.parse('€öäü'); + chai.expect(parser.currentState).equal(STATE.DCS_IGNORE); + testTerminal.compare([]); + parser.reset(); + testTerminal.clear(); + }); + it('DCS_PASSTHROUGH error', function (): void { + parser.reset(); + testTerminal.clear(); + parser.currentState = STATE.DCS_PASSTHROUGH; + parser.parse('€öäü'); + chai.expect(parser.currentState).equal(STATE.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 = STATE.GROUND; + parser.parse('\x1e'); + chai.expect(parser.currentState).equal(STATE.GROUND); + testTerminal.compare([]); + parser.reset(); + testTerminal.clear(); + }); }); -}); -let errorTerminal1 = function(): void {}; -errorTerminal1.prototype = testTerminal; -let errTerminal1 = new errorTerminal1(); -errTerminal1.inst_E = function(e: any): void { - this.calls.push(['error', e]); -}; -let errParser1 = new AnsiParser(errTerminal1); + 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); -let errorTerminal2 = function(): void {}; -errorTerminal2.prototype = testTerminal; -let errTerminal2 = new errorTerminal2(); -errTerminal2.inst_E = function(e: any): any { - this.calls.push(['error', e]); - return true; // --> abort parsing -}; -let errParser2 = new AnsiParser(errTerminal2); + 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); -describe('error tests', function(): void { - it('CSI_PARAM unicode error - inst_E output w/o abort', function (): void { - errParser1.parse('\x1b[<31;5€normal print'); - errTerminal1.compare([ - ['error', { - pos: 7, - character: '€', - state: 4, - print: -1, - dcs: -1, - osc: '', - collect: '<', - params: [31, 5]}], - ['print', 'normal print'] - ]); - parser.reset(); - testTerminal.clear(); - }); - it('CSI_PARAM unicode error - inst_E output with abort', function (): void { - errParser2.parse('\x1b[<31;5€no print'); - errTerminal2.compare([ - ['error', { - pos: 7, - character: '€', - state: 4, - print: -1, - dcs: -1, - osc: '', - collect: '<', - params: [31, 5]}] - ]); - parser.reset(); - testTerminal.clear(); + 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', { + pos: 7, + code: '€'.charCodeAt(0), + state: 4, + print: -1, + dcs: -1, + osc: '', + collect: '<', + 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', { + pos: 7, + code: '€'.charCodeAt(0), + state: 4, + print: -1, + dcs: -1, + osc: '', + collect: '<', + params: [31, 5]}] + ]); + parser.reset(); + testTerminal.clear(); + }); }); + }); diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts index 6facddeab8..072ab882d8 100644 --- a/src/EscapeSequenceParser.ts +++ b/src/EscapeSequenceParser.ts @@ -1,17 +1,62 @@ +import { IInputHandler, IInputHandlingTerminal } from './Types'; +import { CHARSETS, DEFAULT_CHARSET } from './Charsets'; +import { C0 } from './EscapeSequences'; + + +// terminal interface for the escape sequence parser export interface IParserTerminal { - inst_p?: (s: string, start: number, end: number) => void; - inst_o?: (s: string) => void; - inst_x?: (flag: string) => void; - inst_c?: (collected: string, params: number[], flag: string) => void; - inst_e?: (collected: string, flag: string) => void; - inst_H?: (collected: string, params: number[], flag: string) => void; - inst_P?: (dcs: string) => void; - inst_U?: () => void; - inst_E?: () => void; // TODO: real signature + actionPrint?: (data: string, start: number, end: number) => void; + actionOSC?: (data: string) => void; + actionExecute?: (flag: string) => void; + actionCSI?: (collected: string, params: number[], flag: string) => void; + actionESC?: (collected: string, flag: string) => void; + actionDCSHook?: (collected: string, params: number[], flag: string) => void; + actionDCSPrint?: (data: string, start: number, end: number) => void; + actionDCSUnhook?: () => void; + actionError?: () => void; // FIXME: real signature and error handling +} + + +// FSM states +export const enum STATE { + GROUND = 0, + ESCAPE, + ESCAPE_INTERMEDIATE, + CSI_ENTRY, + CSI_PARAM, + CSI_INTERMEDIATE, + CSI_IGNORE, + SOS_PM_APC_STRING, + OSC_STRING, + DCS_ENTRY, + DCS_PARAM, + DCS_IGNORE, + DCS_INTERMEDIATE, + DCS_PASSTHROUGH +} + +// FSM actions +export const enum ACTION { + ignore = 0, + error, + print, + execute, + osc_start, + osc_put, + osc_end, + csi_dispatch, + param, + collect, + esc_dispatch, + clear, + dcs_hook, + dcs_put, + dcs_unhook } -export function r(a: number, b: number): number[] { +// number range macro +function r(a: number, b: number): number[] { let c = b - a; let arr = new Array(c); while (c--) { @@ -21,15 +66,20 @@ export function r(a: number, b: number): number[] { } +// transition table of the FSM +// TODO: fallback to array export class TransitionTable { public table: Uint8Array; + constructor(length: number) { this.table = new Uint8Array(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_list(inps: number[], state: number, action: number | null, next: number | null): void { + + 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); } @@ -37,125 +87,139 @@ export class TransitionTable { } +// default definitions of printable and executable characters let PRINTABLES = r(0x20, 0x7f); let EXECUTABLES = r(0x00, 0x18); EXECUTABLES.push(0x19); EXECUTABLES.concat(r(0x1c, 0x20)); +// default transition of the FSM is [error, GROUND] +let DEFAULT_TRANSITION = ACTION.error << 4 | STATE.GROUND; + +// default DEC/ANSI compatible state transition table +// as defined by https://vt100.net/emu/dec_ansi_parser +export const VT500_TRANSITION_TABLE = (function (): TransitionTable { + let table: TransitionTable = new TransitionTable(4095); -export const TRANSITION_TABLE = (function (): TransitionTable { - let t: TransitionTable = new TransitionTable(4095); + let states: number[] = r(STATE.GROUND, STATE.DCS_PASSTHROUGH + 1); + let state: any; // table with default transition [any] --> [error, GROUND] - for (let state = 0; state < 14; ++state) { + 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) { - t[state << 8 | code] = 16; + table[state << 8 | code] = DEFAULT_TRANSITION; } } // apply transitions // printables - t.add_list(PRINTABLES, 0, 2, 0); + table.addMany(PRINTABLES, STATE.GROUND, ACTION.print, STATE.GROUND); // global anywhere rules - for (let state = 0; state < 14; ++state) { - t.add_list([0x18, 0x1a, 0x99, 0x9a], state, 3, 0); - t.add_list(r(0x80, 0x90), state, 3, 0); - t.add_list(r(0x90, 0x98), state, 3, 0); - t.add(0x9c, state, 0, 0); // ST as terminator - t.add(0x1b, state, 11, 1); // ESC - t.add(0x9d, state, 4, 8); // OSC - t.add_list([0x98, 0x9e, 0x9f], state, 0, 7); - t.add(0x9b, state, 11, 3); // CSI - t.add(0x90, state, 11, 9); // DCS + for (state in states) { + table.addMany([0x18, 0x1a, 0x99, 0x9a], state, ACTION.execute, STATE.GROUND); + table.addMany(r(0x80, 0x90), state, ACTION.execute, STATE.GROUND); + table.addMany(r(0x90, 0x98), state, ACTION.execute, STATE.GROUND); + table.add(0x9c, state, ACTION.ignore, STATE.GROUND); // ST as terminator + table.add(0x1b, state, ACTION.clear, STATE.ESCAPE); // ESC + table.add(0x9d, state, ACTION.osc_start, STATE.OSC_STRING); // OSC + table.addMany([0x98, 0x9e, 0x9f], state, ACTION.ignore, STATE.SOS_PM_APC_STRING); + table.add(0x9b, state, ACTION.clear, STATE.CSI_ENTRY); // CSI + table.add(0x90, state, ACTION.clear, STATE.DCS_ENTRY); // DCS } // rules for executables and 7f - t.add_list(EXECUTABLES, 0, 3, 0); - t.add_list(EXECUTABLES, 1, 3, 1); - t.add(0x7f, 1, null, 1); - t.add_list(EXECUTABLES, 8, null, 8); - t.add_list(EXECUTABLES, 3, 3, 3); - t.add(0x7f, 3, null, 3); - t.add_list(EXECUTABLES, 4, 3, 4); - t.add(0x7f, 4, null, 4); - t.add_list(EXECUTABLES, 6, 3, 6); - t.add_list(EXECUTABLES, 5, 3, 5); - t.add(0x7f, 5, null, 5); - t.add_list(EXECUTABLES, 2, 3, 2); - t.add(0x7f, 2, null, 2); + table.addMany(EXECUTABLES, STATE.GROUND, ACTION.execute, STATE.GROUND); + table.addMany(EXECUTABLES, STATE.ESCAPE, ACTION.execute, STATE.ESCAPE); + table.add(0x7f, STATE.ESCAPE, ACTION.ignore, STATE.ESCAPE); + table.addMany(EXECUTABLES, STATE.OSC_STRING, ACTION.ignore, STATE.OSC_STRING); + table.addMany(EXECUTABLES, STATE.CSI_ENTRY, ACTION.execute, STATE.CSI_ENTRY); + table.add(0x7f, STATE.CSI_ENTRY, ACTION.ignore, STATE.CSI_ENTRY); + table.addMany(EXECUTABLES, STATE.CSI_PARAM, ACTION.execute, STATE.CSI_PARAM); + table.add(0x7f, STATE.CSI_PARAM, ACTION.ignore, STATE.CSI_PARAM); + table.addMany(EXECUTABLES, STATE.CSI_IGNORE, ACTION.execute, STATE.CSI_IGNORE); + table.addMany(EXECUTABLES, STATE.CSI_INTERMEDIATE, ACTION.execute, STATE.CSI_INTERMEDIATE); + table.add(0x7f, STATE.CSI_INTERMEDIATE, ACTION.ignore, STATE.CSI_INTERMEDIATE); + table.addMany(EXECUTABLES, STATE.ESCAPE_INTERMEDIATE, ACTION.execute, STATE.ESCAPE_INTERMEDIATE); + table.add(0x7f, STATE.ESCAPE_INTERMEDIATE, ACTION.ignore, STATE.ESCAPE_INTERMEDIATE); // osc - t.add(0x5d, 1, 4, 8); - t.add_list(PRINTABLES, 8, 5, 8); - t.add(0x7f, 8, 5, 8); - t.add_list([0x9c, 0x1b, 0x18, 0x1a, 0x07], 8, 6, 0); - t.add_list(r(0x1c, 0x20), 8, 0, 8); + table.add(0x5d, STATE.ESCAPE, ACTION.osc_start, STATE.OSC_STRING); + table.addMany(PRINTABLES, STATE.OSC_STRING, ACTION.osc_put, STATE.OSC_STRING); + table.add(0x7f, STATE.OSC_STRING, ACTION.osc_put, STATE.OSC_STRING); + table.addMany([0x9c, 0x1b, 0x18, 0x1a, 0x07], STATE.OSC_STRING, ACTION.osc_end, STATE.GROUND); + table.addMany(r(0x1c, 0x20), STATE.OSC_STRING, ACTION.ignore, STATE.OSC_STRING); // sos/pm/apc does nothing - t.add_list([0x58, 0x5e, 0x5f], 1, 0, 7); - t.add_list(PRINTABLES, 7, null, 7); - t.add_list(EXECUTABLES, 7, null, 7); - t.add(0x9c, 7, 0, 0); + table.addMany([0x58, 0x5e, 0x5f], STATE.ESCAPE, ACTION.ignore, STATE.SOS_PM_APC_STRING); + table.addMany(PRINTABLES, STATE.SOS_PM_APC_STRING, ACTION.ignore, STATE.SOS_PM_APC_STRING); + table.addMany(EXECUTABLES, STATE.SOS_PM_APC_STRING, ACTION.ignore, STATE.SOS_PM_APC_STRING); + table.add(0x9c, STATE.SOS_PM_APC_STRING, ACTION.ignore, STATE.GROUND); // csi entries - t.add(0x5b, 1, 11, 3); - t.add_list(r(0x40, 0x7f), 3, 7, 0); - t.add_list(r(0x30, 0x3a), 3, 8, 4); - t.add(0x3b, 3, 8, 4); - t.add_list([0x3c, 0x3d, 0x3e, 0x3f], 3, 9, 4); - t.add_list(r(0x30, 0x3a), 4, 8, 4); - t.add(0x3b, 4, 8, 4); - t.add_list(r(0x40, 0x7f), 4, 7, 0); - t.add_list([0x3a, 0x3c, 0x3d, 0x3e, 0x3f], 4, 0, 6); - t.add_list(r(0x20, 0x40), 6, null, 6); - t.add(0x7f, 6, null, 6); - t.add_list(r(0x40, 0x7f), 6, 0, 0); - t.add(0x3a, 3, 0, 6); - t.add_list(r(0x20, 0x30), 3, 9, 5); - t.add_list(r(0x20, 0x30), 5, 9, 5); - t.add_list(r(0x30, 0x40), 5, 0, 6); - t.add_list(r(0x40, 0x7f), 5, 7, 0); - t.add_list(r(0x20, 0x30), 4, 9, 5); + table.add(0x5b, STATE.ESCAPE, ACTION.clear, STATE.CSI_ENTRY); + table.addMany(r(0x40, 0x7f), STATE.CSI_ENTRY, ACTION.csi_dispatch, STATE.GROUND); + table.addMany(r(0x30, 0x3a), STATE.CSI_ENTRY, ACTION.param, STATE.CSI_PARAM); + table.add(0x3b, STATE.CSI_ENTRY, ACTION.param, STATE.CSI_PARAM); + table.addMany([0x3c, 0x3d, 0x3e, 0x3f], STATE.CSI_ENTRY, ACTION.collect, STATE.CSI_PARAM); + table.addMany(r(0x30, 0x3a), STATE.CSI_PARAM, ACTION.param, STATE.CSI_PARAM); + table.add(0x3b, STATE.CSI_PARAM, ACTION.param, STATE.CSI_PARAM); + table.addMany(r(0x40, 0x7f), STATE.CSI_PARAM, ACTION.csi_dispatch, STATE.GROUND); + table.addMany([0x3a, 0x3c, 0x3d, 0x3e, 0x3f], STATE.CSI_PARAM, ACTION.ignore, STATE.CSI_IGNORE); + table.addMany(r(0x20, 0x40), STATE.CSI_IGNORE, null, STATE.CSI_IGNORE); + table.add(0x7f, STATE.CSI_IGNORE, null, STATE.CSI_IGNORE); + table.addMany(r(0x40, 0x7f), STATE.CSI_IGNORE, ACTION.ignore, STATE.GROUND); + table.add(0x3a, STATE.CSI_ENTRY, ACTION.ignore, STATE.CSI_IGNORE); + table.addMany(r(0x20, 0x30), STATE.CSI_ENTRY, ACTION.collect, STATE.CSI_INTERMEDIATE); + table.addMany(r(0x20, 0x30), STATE.CSI_INTERMEDIATE, ACTION.collect, STATE.CSI_INTERMEDIATE); + table.addMany(r(0x30, 0x40), STATE.CSI_INTERMEDIATE, ACTION.ignore, STATE.CSI_IGNORE); + table.addMany(r(0x40, 0x7f), STATE.CSI_INTERMEDIATE, ACTION.csi_dispatch, STATE.GROUND); + table.addMany(r(0x20, 0x30), STATE.CSI_PARAM, ACTION.collect, STATE.CSI_INTERMEDIATE); // esc_intermediate - t.add_list(r(0x20, 0x30), 1, 9, 2); - t.add_list(r(0x20, 0x30), 2, 9, 2); - t.add_list(r(0x30, 0x7f), 2, 10, 0); - t.add_list(r(0x30, 0x50), 1, 10, 0); - t.add_list([0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x59, 0x5a, 0x5c], 1, 10, 0); - t.add_list(r(0x60, 0x7f), 1, 10, 0); + table.addMany(r(0x20, 0x30), STATE.ESCAPE, ACTION.collect, STATE.ESCAPE_INTERMEDIATE); + table.addMany(r(0x20, 0x30), STATE.ESCAPE_INTERMEDIATE, ACTION.collect, STATE.ESCAPE_INTERMEDIATE); + table.addMany(r(0x30, 0x7f), STATE.ESCAPE_INTERMEDIATE, ACTION.esc_dispatch, STATE.GROUND); + table.addMany(r(0x30, 0x50), STATE.ESCAPE, ACTION.esc_dispatch, STATE.GROUND); + table.addMany(r(0x51, 0x58), STATE.ESCAPE, ACTION.esc_dispatch, STATE.GROUND); + table.addMany([0x59, 0x5a, 0x5c], STATE.ESCAPE, ACTION.esc_dispatch, STATE.GROUND); + table.addMany(r(0x60, 0x7f), STATE.ESCAPE, ACTION.esc_dispatch, STATE.GROUND); // dcs entry - t.add(0x50, 1, 11, 9); - t.add_list(EXECUTABLES, 9, null, 9); - t.add(0x7f, 9, null, 9); - t.add_list(r(0x1c, 0x20), 9, null, 9); - t.add_list(r(0x20, 0x30), 9, 9, 12); - t.add(0x3a, 9, 0, 11); - t.add_list(r(0x30, 0x3a), 9, 8, 10); - t.add(0x3b, 9, 8, 10); - t.add_list([0x3c, 0x3d, 0x3e, 0x3f], 9, 9, 10); - t.add_list(EXECUTABLES, 11, null, 11); - t.add_list(r(0x20, 0x80), 11, null, 11); - t.add_list(r(0x1c, 0x20), 11, null, 11); - t.add_list(EXECUTABLES, 10, null, 10); - t.add(0x7f, 10, null, 10); - t.add_list(r(0x1c, 0x20), 10, null, 10); - t.add_list(r(0x30, 0x3a), 10, 8, 10); - t.add(0x3b, 10, 8, 10); - t.add_list([0x3a, 0x3c, 0x3d, 0x3e, 0x3f], 10, 0, 11); - t.add_list(r(0x20, 0x30), 10, 9, 12); - t.add_list(EXECUTABLES, 12, null, 12); - t.add(0x7f, 12, null, 12); - t.add_list(r(0x1c, 0x20), 12, null, 12); - t.add_list(r(0x20, 0x30), 12, 9, 12); - t.add_list(r(0x30, 0x40), 12, 0, 11); - t.add_list(r(0x40, 0x7f), 12, 12, 13); - t.add_list(r(0x40, 0x7f), 10, 12, 13); - t.add_list(r(0x40, 0x7f), 9, 12, 13); - t.add_list(EXECUTABLES, 13, 13, 13); - t.add_list(PRINTABLES, 13, 13, 13); - t.add(0x7f, 13, null, 13); - t.add_list([0x1b, 0x9c], 13, 14, 0); - - return t; + table.add(0x50, STATE.ESCAPE, ACTION.clear, STATE.DCS_ENTRY); + table.addMany(EXECUTABLES, STATE.DCS_ENTRY, ACTION.ignore, STATE.DCS_ENTRY); + table.add(0x7f, STATE.DCS_ENTRY, ACTION.ignore, STATE.DCS_ENTRY); + table.addMany(r(0x1c, 0x20), STATE.DCS_ENTRY, ACTION.ignore, STATE.DCS_ENTRY); + table.addMany(r(0x20, 0x30), STATE.DCS_ENTRY, ACTION.collect, STATE.DCS_INTERMEDIATE); + table.add(0x3a, STATE.DCS_ENTRY, ACTION.ignore, STATE.DCS_IGNORE); + table.addMany(r(0x30, 0x3a), STATE.DCS_ENTRY, ACTION.param, STATE.DCS_PARAM); + table.add(0x3b, STATE.DCS_ENTRY, ACTION.param, STATE.DCS_PARAM); + table.addMany([0x3c, 0x3d, 0x3e, 0x3f], STATE.DCS_ENTRY, ACTION.collect, STATE.DCS_PARAM); + table.addMany(EXECUTABLES, STATE.DCS_IGNORE, ACTION.ignore, STATE.DCS_IGNORE); + table.addMany(r(0x20, 0x80), STATE.DCS_IGNORE, ACTION.ignore, STATE.DCS_IGNORE); + table.addMany(r(0x1c, 0x20), STATE.DCS_IGNORE, ACTION.ignore, STATE.DCS_IGNORE); + table.addMany(EXECUTABLES, STATE.DCS_PARAM, ACTION.ignore, STATE.DCS_PARAM); + table.add(0x7f, STATE.DCS_PARAM, ACTION.ignore, STATE.DCS_PARAM); + table.addMany(r(0x1c, 0x20), STATE.DCS_PARAM, ACTION.ignore, STATE.DCS_PARAM); + table.addMany(r(0x30, 0x3a), STATE.DCS_PARAM, ACTION.param, STATE.DCS_PARAM); + table.add(0x3b, STATE.DCS_PARAM, ACTION.param, STATE.DCS_PARAM); + table.addMany([0x3a, 0x3c, 0x3d, 0x3e, 0x3f], STATE.DCS_PARAM, ACTION.ignore, STATE.DCS_IGNORE); + table.addMany(r(0x20, 0x30), STATE.DCS_PARAM, ACTION.collect, STATE.DCS_INTERMEDIATE); + table.addMany(EXECUTABLES, STATE.DCS_INTERMEDIATE, ACTION.ignore, STATE.DCS_INTERMEDIATE); + table.add(0x7f, STATE.DCS_INTERMEDIATE, ACTION.ignore, STATE.DCS_INTERMEDIATE); + table.addMany(r(0x1c, 0x20), STATE.DCS_INTERMEDIATE, ACTION.ignore, STATE.DCS_INTERMEDIATE); + table.addMany(r(0x20, 0x30), STATE.DCS_INTERMEDIATE, ACTION.collect, STATE.DCS_INTERMEDIATE); + table.addMany(r(0x30, 0x40), STATE.DCS_INTERMEDIATE, ACTION.ignore, STATE.DCS_IGNORE); + table.addMany(r(0x40, 0x7f), STATE.DCS_INTERMEDIATE, ACTION.dcs_hook, STATE.DCS_PASSTHROUGH); + table.addMany(r(0x40, 0x7f), STATE.DCS_PARAM, ACTION.dcs_hook, STATE.DCS_PASSTHROUGH); + table.addMany(r(0x40, 0x7f), STATE.DCS_ENTRY, ACTION.dcs_hook, STATE.DCS_PASSTHROUGH); + table.addMany(EXECUTABLES, STATE.DCS_PASSTHROUGH, ACTION.dcs_put, STATE.DCS_PASSTHROUGH); + table.addMany(PRINTABLES, STATE.DCS_PASSTHROUGH, ACTION.dcs_put, STATE.DCS_PASSTHROUGH); + table.add(0x7f, STATE.DCS_PASSTHROUGH, ACTION.ignore, STATE.DCS_PASSTHROUGH); + table.addMany([0x1b, 0x9c], STATE.DCS_PASSTHROUGH, ACTION.dcs_unhook, STATE.GROUND); + + return table; })(); -export class AnsiParser { + +// default transition table points to global object +// Q: Copy table to allow custom sequences w'o changing global object? +export class EscapeSequenceParser { public initialState: number; public currentState: number; public transitions: TransitionTable; @@ -163,29 +227,34 @@ export class AnsiParser { public params: number[]; public collected: string; public term: any; - constructor(terminal: IParserTerminal) { - this.initialState = 0; - this.currentState = this.initialState | 0; - this.transitions = new TransitionTable(4095); - this.transitions.table.set(TRANSITION_TABLE.table); + constructor( + terminal?: IParserTerminal | any, + transitions: TransitionTable = VT500_TRANSITION_TABLE) + { + this.initialState = STATE.GROUND; + this.currentState = this.initialState; + this.transitions = transitions; this.osc = ''; this.params = [0]; this.collected = ''; this.term = terminal || {}; - let instructions = ['inst_p', 'inst_o', 'inst_x', 'inst_c', - 'inst_e', 'inst_H', 'inst_P', 'inst_U', 'inst_E']; + let instructions = [ + 'actionPrint', 'actionOSC', 'actionExecute', 'actionCSI', 'actionESC', + 'actionDCSHook', 'actionDCSPrint', 'actionDCSUnhook', 'actionError']; for (let i = 0; i < instructions.length; ++i) { if (!(instructions[i] in this.term)) { this.term[instructions[i]] = function(): void {}; } } } + reset(): void { this.currentState = this.initialState; this.osc = ''; this.params = [0]; this.collected = ''; } + parse(s: string): void { let code = 0; let transition = 0; @@ -204,79 +273,81 @@ export class AnsiParser { let l = s.length; for (let i = 0; i < l; ++i) { code = s.charCodeAt(i); + // shortcut for most chars (print action) - if (currentState === 0 && (code > 0x1f && code < 0x80)) { + if (currentState === STATE.GROUND && (code > 0x1f && code < 0x80)) { printed = (~printed) ? printed : i; continue; } - if (currentState === 4) { - if (code === 0x3b) { - params.push(0); - continue; - } - if (code > 0x2f && code < 0x39) { - params[params.length - 1] = params[params.length - 1] * 10 + code - 48; - continue; - } + + // shortcut for CSI params + if (currentState === STATE.CSI_PARAM && (code > 0x2f && code < 0x39)) { + params[params.length - 1] = params[params.length - 1] * 10 + code - 48; + continue; } - transition = ((code < 0xa0) ? (table[currentState << 8 | code]) : 16); + + // normal transition & action lookup + transition = (code < 0xa0) ? (table[currentState << 8 | code]) : DEFAULT_TRANSITION; switch (transition >> 4) { - case 2: // print + case ACTION.print: printed = (~printed) ? printed : i; break; - case 3: // execute - if (printed + 1) { - this.term.inst_p(s, printed, i); + case ACTION.execute: + if (~printed) { + this.term.actionPrint(s, printed, i); printed = -1; } - this.term.inst_x(String.fromCharCode(code)); + this.term.actionExecute(String.fromCharCode(code)); break; - case 0: // ignore - // handle leftover print and dcs chars - if (printed + 1) { - this.term.inst_p(s, printed, i); + case ACTION.ignore: + // handle leftover print or dcs chars + if (~printed) { + this.term.actionPrint(s, printed, i); printed = -1; - } else if (dcs + 1) { - this.term.inst_P(s.substring(dcs, i)); + } else if (~dcs) { + this.term.actionDCSPrint(s, dcs, i); dcs = -1; } break; - case 1: // error - // handle unicode chars in write buffers w'o state change + case ACTION.error: + // chars higher than 0x9f are handled by this action to + // keep the lookup table small if (code > 0x9f) { switch (currentState) { - case 0: // GROUND -> add char to print string + case STATE.GROUND: // add char to print string printed = (~printed) ? printed : i; break; - case 8: // OSC_STRING -> add char to osc string + case STATE.OSC_STRING: // add char to osc string osc += String.fromCharCode(code); - transition |= 8; + transition |= STATE.OSC_STRING; break; - case 6: // CSI_IGNORE -> ignore char - transition |= 6; + case STATE.CSI_IGNORE: // ignore char + transition |= STATE.CSI_IGNORE; break; - case 11: // DCS_IGNORE -> ignore char - transition |= 11; + case STATE.DCS_IGNORE: // ignore char + transition |= STATE.DCS_IGNORE; break; - case 13: // DCS_PASSTHROUGH -> add char to dcs - if (!(~dcs)) dcs = i | 0; - transition |= 13; + case STATE.DCS_PASSTHROUGH: // add char to dcs string + dcs = (~dcs) ? dcs : i; + transition |= STATE.DCS_PASSTHROUGH; break; - default: // real error + default: error = true; } - } else { // real error + } else { error = true; } + // if we end up here a real error happened + // FIXME: eval and inject return values if (error) { - if (this.term.inst_E( + if (this.term.actionError( { - pos: i, // position in parse string - character: String.fromCharCode(code), // wrong character - state: currentState, // in state - print: printed, // print buffer - dcs: dcs, // dcs buffer - osc: osc, // osc buffer + pos: i, // position in string + code: code, // actual character code + state: currentState, // current state + print: printed, // print buffer start index + dcs: dcs, // dcs buffer start index + osc: osc, // osc string buffer collect: collected, // collect buffer params: params // params buffer })) { @@ -285,22 +356,22 @@ export class AnsiParser { error = false; } break; - case 7: // csi_dispatch - this.term.inst_c(collected, params, String.fromCharCode(code)); + case ACTION.csi_dispatch: + this.term.actionCSI(collected, params, String.fromCharCode(code)); break; - case 8: // param + case ACTION.param: if (code === 0x3b) params.push(0); else params[params.length - 1] = params[params.length - 1] * 10 + code - 48; break; - case 9: // collect + case ACTION.collect: collected += String.fromCharCode(code); break; - case 10: // esc_dispatch - this.term.inst_e(collected, String.fromCharCode(code)); + case ACTION.esc_dispatch: + this.term.actionESC(collected, String.fromCharCode(code)); break; - case 11: // clear + case ACTION.clear: if (~printed) { - this.term.inst_p(s, printed, i); + this.term.actionPrint(s, printed, i); printed = -1; } osc = ''; @@ -308,34 +379,34 @@ export class AnsiParser { collected = ''; dcs = -1; break; - case 12: // dcs_hook - this.term.inst_H(collected, params, String.fromCharCode(code)); + case ACTION.dcs_hook: + this.term.actionDCSHook(collected, params, String.fromCharCode(code)); break; - case 13: // dcs_put - if (!(~dcs)) dcs = i; + case ACTION.dcs_put: + dcs = (~dcs) ? dcs : i; break; - case 14: // dcs_unhook - if (~dcs) this.term.inst_P(s.substring(dcs, i)); - this.term.inst_U(); - if (code === 0x1b) transition |= 1; + case ACTION.dcs_unhook: + if (~dcs) this.term.actionDCSPrint(s, dcs, i); + this.term.actionDCSUnhook(); + if (code === 0x1b) transition |= STATE.ESCAPE; osc = ''; params = [0]; collected = ''; dcs = -1; break; - case 4: // osc_start + case ACTION.osc_start: if (~printed) { - this.term.inst_p(s, printed, i); + this.term.actionPrint(s, printed, i); printed = -1; } osc = ''; break; - case 5: // osc_put + case ACTION.osc_put: osc += s.charAt(i); break; - case 6: // osc_end - if (osc && code !== 0x18 && code !== 0x1a) this.term.inst_o(osc); - if (code === 0x1b) transition |= 1; + case ACTION.osc_end: + if (osc && code !== 0x18 && code !== 0x1a) this.term.actionOSC(osc); + if (code === 0x1b) transition |= STATE.ESCAPE; osc = ''; params = [0]; collected = ''; @@ -346,10 +417,10 @@ export class AnsiParser { } // push leftover pushable buffers to terminal - if (!currentState && (printed + 1)) { - this.term.inst_p(s, printed, s.length); - } else if (currentState === 13 && (dcs + 1)) { - this.term.inst_P(s.substring(dcs)); + if (currentState === STATE.GROUND && ~printed) { + this.term.actionPrint(s, printed, s.length); + } else if (currentState === STATE.DCS_PASSTHROUGH && ~dcs) { + this.term.actionDCSPrint(s, dcs, s.length); } // save non pushable buffers @@ -363,31 +434,28 @@ export class AnsiParser { } - - - -import { IInputHandler, IInputHandlingTerminal } from './Types'; -import { CHARSETS, DEFAULT_CHARSET } from './Charsets'; -import { C0 } from './EscapeSequences'; - // 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: AnsiParser; + private _parser: EscapeSequenceParser; private _terminal: any; private _inputHandler: IInputHandler; - constructor(_terminal: any, _inputHandler: IInputHandler) { - this._parser = new AnsiParser(this); + constructor(_inputHandler: IInputHandler, _terminal: any) { + this._parser = new EscapeSequenceParser(this); this._terminal = _terminal; this._inputHandler = _inputHandler; } - write(data: string): void { + parse(data: string): void { const cursorStartX = this._terminal.buffer.x; const cursorStartY = this._terminal.buffer.y; if (this._terminal.debug) { this._terminal.log('data: ' + data); } + // apply leftover surrogate high from last write if (this._terminal.surrogate_high) { data = this._terminal.surrogate_high + data; @@ -401,8 +469,7 @@ export class ParserTerminal implements IParserTerminal { } } - inst_p(data: string, start: number, end: number): void { - // const l = data.length; + actionPrint(data: string, start: number, end: number): void { let ch; let code; let low; @@ -429,14 +496,19 @@ export class ParserTerminal implements IParserTerminal { } } - inst_o(data: string): void { - let params = data.split(';'); - switch (parseInt(params[0])) { + 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 (params[1]) { - this._terminal.title = params[1]; + if (content) { + this._terminal.title = content; this._terminal.handleTitle(this._terminal.title); } break; @@ -487,11 +559,13 @@ export class ParserTerminal implements IParserTerminal { } } - inst_x(flag: string): void { + 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: return this._inputHandler.lineFeed(); - case C0.VT: return this._inputHandler.lineFeed(); + 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(); @@ -504,7 +578,7 @@ export class ParserTerminal implements IParserTerminal { this._terminal.error('Unknown EXEC flag: %s.', flag); } - inst_c(collected: string, params: number[], flag: string): void { + actionCSI(collected: string, params: number[], flag: string): void { this._terminal.prefix = collected; switch (flag) { case '@': return this._inputHandler.insertChars(params); @@ -524,6 +598,7 @@ export class ParserTerminal implements IParserTerminal { 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); } @@ -559,33 +634,26 @@ export class ParserTerminal implements IParserTerminal { this._terminal.error('Unknown CSI code: %s %s %s.', collected, params, flag); } - inst_e(collected: string, flag: string): void { - let cs; - + 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) - this._inputHandler.saveCursor(); - return; + return this._inputHandler.saveCursor(); case '8': // Restore Cursor (DECRC) - this._inputHandler.restoreCursor(); - return; + return this._inputHandler.restoreCursor(); // case '9': // Forward Index (DECFI), VT420 and up - not supported case 'D': // Index (IND is 0x84) - this._terminal.index(); - return; + 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) - (this._terminal).tabSet(); - return; + return (this._terminal).tabSet(); case 'M': // Reverse Index (RI is 0x8d) - this._terminal.reverseIndex(); - return; + 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; @@ -620,20 +688,15 @@ export class ParserTerminal implements IParserTerminal { // 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). - this._terminal.setgLevel(2); - return; + return this._terminal.setgLevel(2); case 'o': // Invoke the G3 Character Set as GL (LS3). - this._terminal.setgLevel(3); - return; + return this._terminal.setgLevel(3); case '|': // Invoke the G3 Character Set as GR (LS3R). - this._terminal.setgLevel(3); - return; + return this._terminal.setgLevel(3); case '}': // Invoke the G2 Character Set as GR (LS2R). - this._terminal.setgLevel(2); - return; + return this._terminal.setgLevel(2); case '~': // Invoke the G1 Character Set as GR (LS1R). - this._terminal.setgLevel(1); - return; + return this._terminal.setgLevel(1); } // case ' ': // switch (flag) { @@ -664,55 +727,59 @@ export class ParserTerminal implements IParserTerminal { // load character sets case '(': // G0 (VT100) - cs = CHARSETS[flag]; - if (!cs) cs = DEFAULT_CHARSET; - this._terminal.setgCharset(0, cs); - return; + return this._terminal.setgCharset(0, CHARSETS[flag] || DEFAULT_CHARSET); case ')': // G1 (VT100) - cs = CHARSETS[flag]; - if (!cs) cs = DEFAULT_CHARSET; - this._terminal.setgCharset(1, cs); - return; + return this._terminal.setgCharset(1, CHARSETS[flag] || DEFAULT_CHARSET); case '*': // G2 (VT220) - cs = CHARSETS[flag]; - if (!cs) cs = DEFAULT_CHARSET; - this._terminal.setgCharset(2, cs); - return; + return this._terminal.setgCharset(2, CHARSETS[flag] || DEFAULT_CHARSET); case '+': // G3 (VT220) - cs = CHARSETS[flag]; - if (!cs) cs = DEFAULT_CHARSET; - this._terminal.setgCharset(3, cs); - return; + return this._terminal.setgCharset(3, CHARSETS[flag] || DEFAULT_CHARSET); case '-': // G1 (VT300) - cs = CHARSETS[flag]; - if (!cs) cs = DEFAULT_CHARSET; - this._terminal.setgCharset(1, cs); - return; + return this._terminal.setgCharset(1, CHARSETS[flag] || DEFAULT_CHARSET); case '.': // G2 (VT300) - if (!cs) cs = DEFAULT_CHARSET; - this._terminal.setgCharset(2, cs); - return; + return this._terminal.setgCharset(2, CHARSETS[flag] || DEFAULT_CHARSET); case '/': // G3 (VT300) - // not supported - how to deal with this? (original code is not reachable) + // 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); } } - inst_H(collected: string, params: number[], flag: string): void { + 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 } - inst_P(dcs: string): void { + unregisterOSCHandler(): void { // TODO } - inst_U(): void { + registerDCSHandler(): void { // TODO } - inst_E(): void { + unregisterDCSHandler(): void { // TODO } } diff --git a/src/Terminal.ts b/src/Terminal.ts index 44c9064333..54548f9ef8 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -334,7 +334,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this._inputHandler = new InputHandler(this); // this._parser = new Parser(this._inputHandler, this); - this._newParser = new ParserTerminal(this, this._inputHandler); + this._newParser = new ParserTerminal(this._inputHandler, this); // Reuse renderer if the Terminal is being recreated via a reset call. this.renderer = this.renderer || null; this.selectionManager = this.selectionManager || null; @@ -1320,7 +1320,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // just sets the state back based on the correct return statement. // const state = this._parser.parse(data); - this._newParser.write(data); + this._newParser.parse(data); // this._parser.setState(state); this.updateRange(this.buffer.y); From a05839501477cdbc76bb99b5bb4038d03237dd0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 24 Apr 2018 12:27:43 +0200 Subject: [PATCH 06/44] move addChar to ParserTerminal --- src/EscapeSequenceParser.ts | 94 +++++++++++++++++++++++++++++++++++-- 1 file changed, 91 insertions(+), 3 deletions(-) diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts index 072ab882d8..8d7a53bc17 100644 --- a/src/EscapeSequenceParser.ts +++ b/src/EscapeSequenceParser.ts @@ -433,7 +433,8 @@ export class EscapeSequenceParser { } } - +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? @@ -473,6 +474,7 @@ export class ParserTerminal implements IParserTerminal { let ch; let code; let low; + const buffer = this._terminal.buffer; for (let i = start; i < end; ++i) { ch = data.charAt(i); code = data.charCodeAt(i); @@ -492,7 +494,93 @@ export class ParserTerminal implements IParserTerminal { if (0xDC00 <= code && code <= 0xDFFF) { continue; } - this._inputHandler.addChar(ch, code); + + // this._inputHandler.addChar(ch, code); + + // calculate print space + // expensive call, therefore we save width in line buffer + const chWidth = wcwidth(code); + + if (this._terminal.charset && this._terminal.charset[ch]) { + ch = this._terminal.charset[ch]; + } + + if (this._terminal.options.screenReaderMode) { + this._terminal.emit('a11y.char', ch); + } + + let row = buffer.y + buffer.ybase; + + // insert combining char in last cell + // FIXME: needs handling after cursor jumps + if (!chWidth && buffer.x) { + // dont overflow left + if (buffer.lines.get(row)[buffer.x - 1]) { + if (!buffer.lines.get(row)[buffer.x - 1][CHAR_DATA_WIDTH_INDEX]) { + // found empty cell after fullwidth, need to go 2 cells back + if (buffer.lines.get(row)[buffer.x - 2]) { + buffer.lines.get(row)[buffer.x - 2][CHAR_DATA_CHAR_INDEX] += ch; + buffer.lines.get(row)[buffer.x - 2][3] = ch.charCodeAt(0); + } + } else { + buffer.lines.get(row)[buffer.x - 1][CHAR_DATA_CHAR_INDEX] += ch; + buffer.lines.get(row)[buffer.x - 1][3] = ch.charCodeAt(0); + } + this._terminal.updateRange(buffer.y); + } + continue; + } + + // goto next line if ch would overflow + // TODO: needs a global min terminal width of 2 + if (buffer.x + chWidth - 1 >= this._terminal.cols) { + // autowrap - DECAWM + if (this._terminal.wraparoundMode) { + buffer.x = 0; + buffer.y++; + if (buffer.y > buffer.scrollBottom) { + buffer.y--; + this._terminal.scroll(true); + } else { + // The line already exists (eg. the initial viewport), mark it as a + // wrapped line + (buffer.lines.get(buffer.y)).isWrapped = true; + } + } else { + if (chWidth === 2) { // FIXME: check for xterm behavior + continue; + } + } + } + row = buffer.y + buffer.ybase; + + // insert mode: move characters to right + if (this._terminal.insertMode) { + // do this twice for a fullwidth char + for (let moves = 0; moves < chWidth; ++moves) { + // remove last cell, if it's width is 0 + // we have to adjust the second last cell as well + const removed = buffer.lines.get(buffer.y + buffer.ybase).pop(); + if (removed[CHAR_DATA_WIDTH_INDEX] === 0 + && buffer.lines.get(row)[this._terminal.cols - 2] + && buffer.lines.get(row)[this._terminal.cols - 2][CHAR_DATA_WIDTH_INDEX] === 2) { + buffer.lines.get(row)[this._terminal.cols - 2] = [this._terminal.curAttr, ' ', 1, ' '.charCodeAt(0)]; + } + + // insert empty cell at cursor + buffer.lines.get(row).splice(buffer.x, 0, [this._terminal.curAttr, ' ', 1, ' '.charCodeAt(0)]); + } + } + + buffer.lines.get(row)[buffer.x] = [this._terminal.curAttr, ch, chWidth, ch.charCodeAt(0)]; + buffer.x++; + this._terminal.updateRange(buffer.y); + + // fullwidth char - set next cell width to zero and advance cursor + if (chWidth === 2) { + buffer.lines.get(row)[buffer.x] = [this._terminal.curAttr, '', 0, undefined]; + buffer.x++; + } } } @@ -573,7 +661,7 @@ export class ParserTerminal implements IParserTerminal { case C0.SO: return this._inputHandler.shiftOut(); case C0.SI: return this._inputHandler.shiftIn(); default: - this._inputHandler.addChar(flag, flag.charCodeAt(0)); + this._inputHandler.addChar(flag, flag.charCodeAt(0)); // TODO: get rid this here } this._terminal.error('Unknown EXEC flag: %s.', flag); } From c0ab2b9a7c0f19b51284291bece10f522df3ecc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 24 Apr 2018 13:06:03 +0200 Subject: [PATCH 07/44] rename enums --- src/EscapeSequenceParser.test.ts | 312 +++++++++++++-------------- src/EscapeSequenceParser.ts | 352 +++++++++++++++---------------- 2 files changed, 332 insertions(+), 332 deletions(-) diff --git a/src/EscapeSequenceParser.test.ts b/src/EscapeSequenceParser.test.ts index 296ac34d4f..50d8d5516c 100644 --- a/src/EscapeSequenceParser.test.ts +++ b/src/EscapeSequenceParser.test.ts @@ -1,4 +1,4 @@ -import { EscapeSequenceParser, IParserTerminal, STATE } from './EscapeSequenceParser'; +import { EscapeSequenceParser, IParserTerminal, ParserState } from './EscapeSequenceParser'; import * as chai from 'chai'; function r(a: number, b: number): string[] { @@ -51,20 +51,20 @@ let testTerminal: ITestTerminal = { }; let states: number[] = [ - STATE.GROUND, - STATE.ESCAPE, - STATE.ESCAPE_INTERMEDIATE, - STATE.CSI_ENTRY, - STATE.CSI_PARAM, - STATE.CSI_INTERMEDIATE, - STATE.CSI_IGNORE, - STATE.SOS_PM_APC_STRING, - STATE.OSC_STRING, - STATE.DCS_ENTRY, - STATE.DCS_PARAM, - STATE.DCS_IGNORE, - STATE.DCS_INTERMEDIATE, - STATE.DCS_PASSTHROUGH + 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; @@ -111,7 +111,7 @@ describe('EscapeSequenceParser', function(): void { parser.collected = '#'; parser.reset(); - chai.expect(parser.currentState).equal(STATE.GROUND); + chai.expect(parser.currentState).equal(ParserState.GROUND); chai.expect(parser.osc).equal(''); chai.expect(parser.params).eql([0]); chai.expect(parser.collected).equal(''); @@ -126,9 +126,9 @@ describe('EscapeSequenceParser', function(): void { exes.concat(['\x19']); exes.concat(r(0x1c, 0x20)); for (let i = 0; i < exes.length; ++i) { - parser.currentState = STATE.GROUND; + parser.currentState = ParserState.GROUND; parser.parse(exes[i]); - chai.expect(parser.currentState).equal(STATE.GROUND); + chai.expect(parser.currentState).equal(ParserState.GROUND); testTerminal.compare([['exe', exes[i]]]); parser.reset(); testTerminal.clear(); @@ -139,9 +139,9 @@ describe('EscapeSequenceParser', function(): void { testTerminal.clear(); let printables = r(0x20, 0x7f); // NOTE: DEL excluded for (let i = 0; i < printables.length; ++i) { - parser.currentState = STATE.GROUND; + parser.currentState = ParserState.GROUND; parser.parse(printables[i]); - chai.expect(parser.currentState).equal(STATE.GROUND); + chai.expect(parser.currentState).equal(ParserState.GROUND); testTerminal.compare([['print', printables[i]]]); parser.reset(); testTerminal.clear(); @@ -163,13 +163,13 @@ describe('EscapeSequenceParser', function(): void { for (let i = 0; i < exes.length; ++i) { parser.currentState = state; parser.parse(exes[i]); - chai.expect(parser.currentState).equal(STATE.GROUND); + 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(STATE.GROUND); + chai.expect(parser.currentState).equal(ParserState.GROUND); testTerminal.compare([]); parser.reset(); testTerminal.clear(); @@ -183,7 +183,7 @@ describe('EscapeSequenceParser', function(): void { parser.params = [23]; parser.collected = '#'; parser.parse('\x1b'); - chai.expect(parser.currentState).equal(STATE.ESCAPE); + chai.expect(parser.currentState).equal(ParserState.ESCAPE); chai.expect(parser.osc).equal(''); chai.expect(parser.params).eql([0]); chai.expect(parser.collected).equal(''); @@ -197,9 +197,9 @@ describe('EscapeSequenceParser', function(): void { exes.concat(['\x19']); exes.concat(r(0x1c, 0x20)); for (let i = 0; i < exes.length; ++i) { - parser.currentState = STATE.ESCAPE; + parser.currentState = ParserState.ESCAPE; parser.parse(exes[i]); - chai.expect(parser.currentState).equal(STATE.ESCAPE); + chai.expect(parser.currentState).equal(ParserState.ESCAPE); testTerminal.compare([['exe', exes[i]]]); parser.reset(); testTerminal.clear(); @@ -208,9 +208,9 @@ describe('EscapeSequenceParser', function(): void { it('state ESCAPE ignore', function (): void { parser.reset(); testTerminal.clear(); - parser.currentState = STATE.ESCAPE; + parser.currentState = ParserState.ESCAPE; parser.parse('\x7f'); - chai.expect(parser.currentState).equal(STATE.ESCAPE); + chai.expect(parser.currentState).equal(ParserState.ESCAPE); testTerminal.compare([]); parser.reset(); testTerminal.clear(); @@ -223,9 +223,9 @@ describe('EscapeSequenceParser', function(): void { dispatches.concat(['\x59', '\x5a', '\x5c']); dispatches.concat(r(0x60, 0x7f)); for (let i = 0; i < dispatches.length; ++i) { - parser.currentState = STATE.ESCAPE; + parser.currentState = ParserState.ESCAPE; parser.parse(dispatches[i]); - chai.expect(parser.currentState).equal(STATE.GROUND); + chai.expect(parser.currentState).equal(ParserState.GROUND); testTerminal.compare([['esc', '', dispatches[i]]]); parser.reset(); testTerminal.clear(); @@ -235,9 +235,9 @@ describe('EscapeSequenceParser', function(): void { parser.reset(); let collect = r(0x20, 0x30); for (let i = 0; i < collect.length; ++i) { - parser.currentState = STATE.ESCAPE; + parser.currentState = ParserState.ESCAPE; parser.parse(collect[i]); - chai.expect(parser.currentState).equal(STATE.ESCAPE_INTERMEDIATE); + chai.expect(parser.currentState).equal(ParserState.ESCAPE_INTERMEDIATE); chai.expect(parser.collected).equal(collect[i]); parser.reset(); } @@ -249,9 +249,9 @@ describe('EscapeSequenceParser', function(): void { exes.concat(['\x19']); exes.concat(r(0x1c, 0x20)); for (let i = 0; i < exes.length; ++i) { - parser.currentState = STATE.ESCAPE_INTERMEDIATE; + parser.currentState = ParserState.ESCAPE_INTERMEDIATE; parser.parse(exes[i]); - chai.expect(parser.currentState).equal(STATE.ESCAPE_INTERMEDIATE); + chai.expect(parser.currentState).equal(ParserState.ESCAPE_INTERMEDIATE); testTerminal.compare([['exe', exes[i]]]); parser.reset(); testTerminal.clear(); @@ -260,9 +260,9 @@ describe('EscapeSequenceParser', function(): void { it('state ESCAPE_INTERMEDIATE ignore', function (): void { parser.reset(); testTerminal.clear(); - parser.currentState = STATE.ESCAPE_INTERMEDIATE; + parser.currentState = ParserState.ESCAPE_INTERMEDIATE; parser.parse('\x7f'); - chai.expect(parser.currentState).equal(STATE.ESCAPE_INTERMEDIATE); + chai.expect(parser.currentState).equal(ParserState.ESCAPE_INTERMEDIATE); testTerminal.compare([]); parser.reset(); testTerminal.clear(); @@ -271,9 +271,9 @@ describe('EscapeSequenceParser', function(): void { parser.reset(); let collect = r(0x20, 0x30); for (let i = 0; i < collect.length; ++i) { - parser.currentState = STATE.ESCAPE_INTERMEDIATE; + parser.currentState = ParserState.ESCAPE_INTERMEDIATE; parser.parse(collect[i]); - chai.expect(parser.currentState).equal(STATE.ESCAPE_INTERMEDIATE); + chai.expect(parser.currentState).equal(ParserState.ESCAPE_INTERMEDIATE); chai.expect(parser.collected).equal(collect[i]); parser.reset(); } @@ -283,9 +283,9 @@ describe('EscapeSequenceParser', function(): void { testTerminal.clear(); let collect = r(0x30, 0x7f); for (let i = 0; i < collect.length; ++i) { - parser.currentState = STATE.ESCAPE_INTERMEDIATE; + parser.currentState = ParserState.ESCAPE_INTERMEDIATE; parser.parse(collect[i]); - chai.expect(parser.currentState).equal(STATE.GROUND); + chai.expect(parser.currentState).equal(ParserState.GROUND); testTerminal.compare([['esc', '', collect[i]]]); parser.reset(); testTerminal.clear(); @@ -294,12 +294,12 @@ describe('EscapeSequenceParser', function(): void { it('trans ANYWHERE/ESCAPE --> CSI_ENTRY with clear', function (): void { parser.reset(); // C0 - parser.currentState = STATE.ESCAPE; + parser.currentState = ParserState.ESCAPE; parser.osc = '#'; parser.params = [123]; parser.collected = '#'; parser.parse('['); - chai.expect(parser.currentState).equal(STATE.CSI_ENTRY); + chai.expect(parser.currentState).equal(ParserState.CSI_ENTRY); chai.expect(parser.osc).equal(''); chai.expect(parser.params).eql([0]); chai.expect(parser.collected).equal(''); @@ -311,7 +311,7 @@ describe('EscapeSequenceParser', function(): void { parser.params = [123]; parser.collected = '#'; parser.parse('\x9b'); - chai.expect(parser.currentState).equal(STATE.CSI_ENTRY); + chai.expect(parser.currentState).equal(ParserState.CSI_ENTRY); chai.expect(parser.osc).equal(''); chai.expect(parser.params).eql([0]); chai.expect(parser.collected).equal(''); @@ -325,9 +325,9 @@ describe('EscapeSequenceParser', function(): void { exes.concat(['\x19']); exes.concat(r(0x1c, 0x20)); for (let i = 0; i < exes.length; ++i) { - parser.currentState = STATE.CSI_ENTRY; + parser.currentState = ParserState.CSI_ENTRY; parser.parse(exes[i]); - chai.expect(parser.currentState).equal(STATE.CSI_ENTRY); + chai.expect(parser.currentState).equal(ParserState.CSI_ENTRY); testTerminal.compare([['exe', exes[i]]]); parser.reset(); testTerminal.clear(); @@ -336,9 +336,9 @@ describe('EscapeSequenceParser', function(): void { it('state CSI_ENTRY ignore', function (): void { parser.reset(); testTerminal.clear(); - parser.currentState = STATE.CSI_ENTRY; + parser.currentState = ParserState.CSI_ENTRY; parser.parse('\x7f'); - chai.expect(parser.currentState).equal(STATE.CSI_ENTRY); + chai.expect(parser.currentState).equal(ParserState.CSI_ENTRY); testTerminal.compare([]); parser.reset(); testTerminal.clear(); @@ -347,9 +347,9 @@ describe('EscapeSequenceParser', function(): void { parser.reset(); let dispatches = r(0x40, 0x7f); for (let i = 0; i < dispatches.length; ++i) { - parser.currentState = STATE.CSI_ENTRY; + parser.currentState = ParserState.CSI_ENTRY; parser.parse(dispatches[i]); - chai.expect(parser.currentState).equal(STATE.GROUND); + chai.expect(parser.currentState).equal(ParserState.GROUND); testTerminal.compare([['csi', '', [0], dispatches[i]]]); parser.reset(); testTerminal.clear(); @@ -360,21 +360,21 @@ describe('EscapeSequenceParser', function(): void { 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 = STATE.CSI_ENTRY; + parser.currentState = ParserState.CSI_ENTRY; parser.parse(params[i]); - chai.expect(parser.currentState).equal(STATE.CSI_PARAM); + chai.expect(parser.currentState).equal(ParserState.CSI_PARAM); chai.expect(parser.params).eql([params[i].charCodeAt(0) - 48]); parser.reset(); } - parser.currentState = STATE.CSI_ENTRY; + parser.currentState = ParserState.CSI_ENTRY; parser.parse('\x3b'); - chai.expect(parser.currentState).equal(STATE.CSI_PARAM); + 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 = STATE.CSI_ENTRY; + parser.currentState = ParserState.CSI_ENTRY; parser.parse(collect[i]); - chai.expect(parser.currentState).equal(STATE.CSI_PARAM); + chai.expect(parser.currentState).equal(ParserState.CSI_PARAM); chai.expect(parser.collected).equal(collect[i]); parser.reset(); } @@ -386,9 +386,9 @@ describe('EscapeSequenceParser', function(): void { exes.concat(['\x19']); exes.concat(r(0x1c, 0x20)); for (let i = 0; i < exes.length; ++i) { - parser.currentState = STATE.CSI_PARAM; + parser.currentState = ParserState.CSI_PARAM; parser.parse(exes[i]); - chai.expect(parser.currentState).equal(STATE.CSI_PARAM); + chai.expect(parser.currentState).equal(ParserState.CSI_PARAM); testTerminal.compare([['exe', exes[i]]]); parser.reset(); testTerminal.clear(); @@ -398,24 +398,24 @@ describe('EscapeSequenceParser', 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 = STATE.CSI_PARAM; + parser.currentState = ParserState.CSI_PARAM; parser.parse(params[i]); - chai.expect(parser.currentState).equal(STATE.CSI_PARAM); + chai.expect(parser.currentState).equal(ParserState.CSI_PARAM); chai.expect(parser.params).eql([params[i].charCodeAt(0) - 48]); parser.reset(); } - parser.currentState = STATE.CSI_PARAM; + parser.currentState = ParserState.CSI_PARAM; parser.parse('\x3b'); - chai.expect(parser.currentState).equal(STATE.CSI_PARAM); + 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 = STATE.CSI_PARAM; + parser.currentState = ParserState.CSI_PARAM; parser.parse('\x7f'); - chai.expect(parser.currentState).equal(STATE.CSI_PARAM); + chai.expect(parser.currentState).equal(ParserState.CSI_PARAM); testTerminal.compare([]); parser.reset(); testTerminal.clear(); @@ -424,10 +424,10 @@ describe('EscapeSequenceParser', function(): void { parser.reset(); let dispatches = r(0x40, 0x7f); for (let i = 0; i < dispatches.length; ++i) { - parser.currentState = STATE.CSI_PARAM; + parser.currentState = ParserState.CSI_PARAM; parser.params = [0, 1]; parser.parse(dispatches[i]); - chai.expect(parser.currentState).equal(STATE.GROUND); + chai.expect(parser.currentState).equal(ParserState.GROUND); testTerminal.compare([['csi', '', [0, 1], dispatches[i]]]); parser.reset(); testTerminal.clear(); @@ -437,9 +437,9 @@ describe('EscapeSequenceParser', function(): void { parser.reset(); let collect = r(0x20, 0x30); for (let i = 0; i < collect.length; ++i) { - parser.currentState = STATE.CSI_ENTRY; + parser.currentState = ParserState.CSI_ENTRY; parser.parse(collect[i]); - chai.expect(parser.currentState).equal(STATE.CSI_INTERMEDIATE); + chai.expect(parser.currentState).equal(ParserState.CSI_INTERMEDIATE); chai.expect(parser.collected).equal(collect[i]); parser.reset(); } @@ -448,9 +448,9 @@ describe('EscapeSequenceParser', function(): void { parser.reset(); let collect = r(0x20, 0x30); for (let i = 0; i < collect.length; ++i) { - parser.currentState = STATE.CSI_PARAM; + parser.currentState = ParserState.CSI_PARAM; parser.parse(collect[i]); - chai.expect(parser.currentState).equal(STATE.CSI_INTERMEDIATE); + chai.expect(parser.currentState).equal(ParserState.CSI_INTERMEDIATE); chai.expect(parser.collected).equal(collect[i]); parser.reset(); } @@ -462,9 +462,9 @@ describe('EscapeSequenceParser', function(): void { exes.concat(['\x19']); exes.concat(r(0x1c, 0x20)); for (let i = 0; i < exes.length; ++i) { - parser.currentState = STATE.CSI_INTERMEDIATE; + parser.currentState = ParserState.CSI_INTERMEDIATE; parser.parse(exes[i]); - chai.expect(parser.currentState).equal(STATE.CSI_INTERMEDIATE); + chai.expect(parser.currentState).equal(ParserState.CSI_INTERMEDIATE); testTerminal.compare([['exe', exes[i]]]); parser.reset(); testTerminal.clear(); @@ -474,9 +474,9 @@ describe('EscapeSequenceParser', function(): void { parser.reset(); let collect = r(0x20, 0x30); for (let i = 0; i < collect.length; ++i) { - parser.currentState = STATE.CSI_INTERMEDIATE; + parser.currentState = ParserState.CSI_INTERMEDIATE; parser.parse(collect[i]); - chai.expect(parser.currentState).equal(STATE.CSI_INTERMEDIATE); + chai.expect(parser.currentState).equal(ParserState.CSI_INTERMEDIATE); chai.expect(parser.collected).equal(collect[i]); parser.reset(); } @@ -484,9 +484,9 @@ describe('EscapeSequenceParser', function(): void { it('state CSI_INTERMEDIATE ignore', function (): void { parser.reset(); testTerminal.clear(); - parser.currentState = STATE.CSI_INTERMEDIATE; + parser.currentState = ParserState.CSI_INTERMEDIATE; parser.parse('\x7f'); - chai.expect(parser.currentState).equal(STATE.CSI_INTERMEDIATE); + chai.expect(parser.currentState).equal(ParserState.CSI_INTERMEDIATE); testTerminal.compare([]); parser.reset(); testTerminal.clear(); @@ -495,10 +495,10 @@ describe('EscapeSequenceParser', function(): void { parser.reset(); let dispatches = r(0x40, 0x7f); for (let i = 0; i < dispatches.length; ++i) { - parser.currentState = STATE.CSI_INTERMEDIATE; + parser.currentState = ParserState.CSI_INTERMEDIATE; parser.params = [0, 1]; parser.parse(dispatches[i]); - chai.expect(parser.currentState).equal(STATE.GROUND); + chai.expect(parser.currentState).equal(ParserState.GROUND); testTerminal.compare([['csi', '', [0, 1], dispatches[i]]]); parser.reset(); testTerminal.clear(); @@ -506,18 +506,18 @@ describe('EscapeSequenceParser', function(): void { }); it('trans CSI_ENTRY --> CSI_IGNORE', function (): void { parser.reset(); - parser.currentState = STATE.CSI_ENTRY; + parser.currentState = ParserState.CSI_ENTRY; parser.parse('\x3a'); - chai.expect(parser.currentState).equal(STATE.CSI_IGNORE); + 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 = STATE.CSI_PARAM; + parser.currentState = ParserState.CSI_PARAM; parser.parse('\x3b' + chars[i]); - chai.expect(parser.currentState).equal(STATE.CSI_IGNORE); + chai.expect(parser.currentState).equal(ParserState.CSI_IGNORE); chai.expect(parser.params).eql([0, 0]); parser.reset(); } @@ -526,9 +526,9 @@ describe('EscapeSequenceParser', function(): void { parser.reset(); let chars = r(0x30, 0x40); for (let i = 0; i < chars.length; ++i) { - parser.currentState = STATE.CSI_INTERMEDIATE; + parser.currentState = ParserState.CSI_INTERMEDIATE; parser.parse(chars[i]); - chai.expect(parser.currentState).equal(STATE.CSI_IGNORE); + chai.expect(parser.currentState).equal(ParserState.CSI_IGNORE); chai.expect(parser.params).eql([0]); parser.reset(); } @@ -540,9 +540,9 @@ describe('EscapeSequenceParser', function(): void { exes.concat(['\x19']); exes.concat(r(0x1c, 0x20)); for (let i = 0; i < exes.length; ++i) { - parser.currentState = STATE.CSI_IGNORE; + parser.currentState = ParserState.CSI_IGNORE; parser.parse(exes[i]); - chai.expect(parser.currentState).equal(STATE.CSI_IGNORE); + chai.expect(parser.currentState).equal(ParserState.CSI_IGNORE); testTerminal.compare([['exe', exes[i]]]); parser.reset(); testTerminal.clear(); @@ -554,9 +554,9 @@ describe('EscapeSequenceParser', function(): void { let ignored = r(0x20, 0x40); ignored.concat(['\x7f']); for (let i = 0; i < ignored.length; ++i) { - parser.currentState = STATE.CSI_IGNORE; + parser.currentState = ParserState.CSI_IGNORE; parser.parse(ignored[i]); - chai.expect(parser.currentState).equal(STATE.CSI_IGNORE); + chai.expect(parser.currentState).equal(ParserState.CSI_IGNORE); testTerminal.compare([]); parser.reset(); testTerminal.clear(); @@ -566,10 +566,10 @@ describe('EscapeSequenceParser', function(): void { parser.reset(); let dispatches = r(0x40, 0x7f); for (let i = 0; i < dispatches.length; ++i) { - parser.currentState = STATE.CSI_IGNORE; + parser.currentState = ParserState.CSI_IGNORE; parser.params = [0, 1]; parser.parse(dispatches[i]); - chai.expect(parser.currentState).equal(STATE.GROUND); + chai.expect(parser.currentState).equal(ParserState.GROUND); testTerminal.compare([]); parser.reset(); testTerminal.clear(); @@ -581,7 +581,7 @@ describe('EscapeSequenceParser', function(): void { let initializers = ['\x58', '\x5e', '\x5f']; for (let i = 0; i < initializers.length; ++i) { parser.parse('\x1b' + initializers[i]); - chai.expect(parser.currentState).equal(STATE.SOS_PM_APC_STRING); + chai.expect(parser.currentState).equal(ParserState.SOS_PM_APC_STRING); parser.reset(); } // C1 @@ -590,7 +590,7 @@ describe('EscapeSequenceParser', function(): void { initializers = ['\x98', '\x9e', '\x9f']; for (let i = 0; i < initializers.length; ++i) { parser.parse(initializers[i]); - chai.expect(parser.currentState).equal(STATE.SOS_PM_APC_STRING); + chai.expect(parser.currentState).equal(ParserState.SOS_PM_APC_STRING); parser.reset(); } } @@ -602,9 +602,9 @@ describe('EscapeSequenceParser', function(): void { ignored.concat(r(0x1c, 0x20)); ignored.concat(r(0x20, 0x80)); for (let i = 0; i < ignored.length; ++i) { - parser.currentState = STATE.SOS_PM_APC_STRING; + parser.currentState = ParserState.SOS_PM_APC_STRING; parser.parse(ignored[i]); - chai.expect(parser.currentState).equal(STATE.SOS_PM_APC_STRING); + chai.expect(parser.currentState).equal(ParserState.SOS_PM_APC_STRING); parser.reset(); } }); @@ -612,13 +612,13 @@ describe('EscapeSequenceParser', function(): void { parser.reset(); // C0 parser.parse('\x1b]'); - chai.expect(parser.currentState).equal(STATE.OSC_STRING); + 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(STATE.OSC_STRING); + chai.expect(parser.currentState).equal(ParserState.OSC_STRING); parser.reset(); } }); @@ -629,9 +629,9 @@ describe('EscapeSequenceParser', function(): void { '\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 = STATE.OSC_STRING; + parser.currentState = ParserState.OSC_STRING; parser.parse(ignored[i]); - chai.expect(parser.currentState).equal(STATE.OSC_STRING); + chai.expect(parser.currentState).equal(ParserState.OSC_STRING); chai.expect(parser.osc).equal(''); parser.reset(); } @@ -640,9 +640,9 @@ describe('EscapeSequenceParser', function(): void { parser.reset(); let puts = r(0x20, 0x80); for (let i = 0; i < puts.length; ++i) { - parser.currentState = STATE.OSC_STRING; + parser.currentState = ParserState.OSC_STRING; parser.parse(puts[i]); - chai.expect(parser.currentState).equal(STATE.OSC_STRING); + chai.expect(parser.currentState).equal(ParserState.OSC_STRING); chai.expect(parser.osc).equal(puts[i]); parser.reset(); } @@ -651,13 +651,13 @@ describe('EscapeSequenceParser', function(): void { parser.reset(); // C0 parser.parse('\x1bP'); - chai.expect(parser.currentState).equal(STATE.DCS_ENTRY); + 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(STATE.DCS_ENTRY); + chai.expect(parser.currentState).equal(ParserState.DCS_ENTRY); parser.reset(); } }); @@ -668,9 +668,9 @@ describe('EscapeSequenceParser', function(): void { '\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 = STATE.DCS_ENTRY; + parser.currentState = ParserState.DCS_ENTRY; parser.parse(ignored[i]); - chai.expect(parser.currentState).equal(STATE.DCS_ENTRY); + chai.expect(parser.currentState).equal(ParserState.DCS_ENTRY); parser.reset(); } }); @@ -679,21 +679,21 @@ describe('EscapeSequenceParser', function(): void { 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 = STATE.DCS_ENTRY; + parser.currentState = ParserState.DCS_ENTRY; parser.parse(params[i]); - chai.expect(parser.currentState).equal(STATE.DCS_PARAM); + chai.expect(parser.currentState).equal(ParserState.DCS_PARAM); chai.expect(parser.params).eql([params[i].charCodeAt(0) - 48]); parser.reset(); } - parser.currentState = STATE.DCS_ENTRY; + parser.currentState = ParserState.DCS_ENTRY; parser.parse('\x3b'); - chai.expect(parser.currentState).equal(STATE.DCS_PARAM); + 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 = STATE.DCS_ENTRY; + parser.currentState = ParserState.DCS_ENTRY; parser.parse(collect[i]); - chai.expect(parser.currentState).equal(STATE.DCS_PARAM); + chai.expect(parser.currentState).equal(ParserState.DCS_PARAM); chai.expect(parser.collected).equal(collect[i]); parser.reset(); } @@ -705,9 +705,9 @@ describe('EscapeSequenceParser', function(): void { '\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 = STATE.DCS_PARAM; + parser.currentState = ParserState.DCS_PARAM; parser.parse(ignored[i]); - chai.expect(parser.currentState).equal(STATE.DCS_PARAM); + chai.expect(parser.currentState).equal(ParserState.DCS_PARAM); parser.reset(); } }); @@ -715,32 +715,32 @@ describe('EscapeSequenceParser', 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 = STATE.DCS_PARAM; + parser.currentState = ParserState.DCS_PARAM; parser.parse(params[i]); - chai.expect(parser.currentState).equal(STATE.DCS_PARAM); + chai.expect(parser.currentState).equal(ParserState.DCS_PARAM); chai.expect(parser.params).eql([params[i].charCodeAt(0) - 48]); parser.reset(); } - parser.currentState = STATE.DCS_PARAM; + parser.currentState = ParserState.DCS_PARAM; parser.parse('\x3b'); - chai.expect(parser.currentState).equal(STATE.DCS_PARAM); + 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 = STATE.DCS_ENTRY; + parser.currentState = ParserState.DCS_ENTRY; parser.parse('\x3a'); - chai.expect(parser.currentState).equal(STATE.DCS_IGNORE); + 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 = STATE.DCS_PARAM; + parser.currentState = ParserState.DCS_PARAM; parser.parse('\x3b' + chars[i]); - chai.expect(parser.currentState).equal(STATE.DCS_IGNORE); + chai.expect(parser.currentState).equal(ParserState.DCS_IGNORE); chai.expect(parser.params).eql([0, 0]); parser.reset(); } @@ -749,9 +749,9 @@ describe('EscapeSequenceParser', function(): void { parser.reset(); let chars = r(0x30, 0x40); for (let i = 0; i < chars.length; ++i) { - parser.currentState = STATE.DCS_INTERMEDIATE; + parser.currentState = ParserState.DCS_INTERMEDIATE; parser.parse(chars[i]); - chai.expect(parser.currentState).equal(STATE.DCS_IGNORE); + chai.expect(parser.currentState).equal(ParserState.DCS_IGNORE); parser.reset(); } }); @@ -763,9 +763,9 @@ describe('EscapeSequenceParser', function(): void { '\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 = STATE.DCS_IGNORE; + parser.currentState = ParserState.DCS_IGNORE; parser.parse(ignored[i]); - chai.expect(parser.currentState).equal(STATE.DCS_IGNORE); + chai.expect(parser.currentState).equal(ParserState.DCS_IGNORE); parser.reset(); } }); @@ -773,9 +773,9 @@ describe('EscapeSequenceParser', function(): void { parser.reset(); let collect = r(0x20, 0x30); for (let i = 0; i < collect.length; ++i) { - parser.currentState = STATE.DCS_ENTRY; + parser.currentState = ParserState.DCS_ENTRY; parser.parse(collect[i]); - chai.expect(parser.currentState).equal(STATE.DCS_INTERMEDIATE); + chai.expect(parser.currentState).equal(ParserState.DCS_INTERMEDIATE); chai.expect(parser.collected).equal(collect[i]); parser.reset(); } @@ -784,9 +784,9 @@ describe('EscapeSequenceParser', function(): void { parser.reset(); let collect = r(0x20, 0x30); for (let i = 0; i < collect.length; ++i) { - parser.currentState = STATE.DCS_PARAM; + parser.currentState = ParserState.DCS_PARAM; parser.parse(collect[i]); - chai.expect(parser.currentState).equal(STATE.DCS_INTERMEDIATE); + chai.expect(parser.currentState).equal(ParserState.DCS_INTERMEDIATE); chai.expect(parser.collected).equal(collect[i]); parser.reset(); } @@ -798,9 +798,9 @@ describe('EscapeSequenceParser', function(): void { '\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 = STATE.DCS_INTERMEDIATE; + parser.currentState = ParserState.DCS_INTERMEDIATE; parser.parse(ignored[i]); - chai.expect(parser.currentState).equal(STATE.DCS_INTERMEDIATE); + chai.expect(parser.currentState).equal(ParserState.DCS_INTERMEDIATE); parser.reset(); } }); @@ -808,9 +808,9 @@ describe('EscapeSequenceParser', function(): void { parser.reset(); let collect = r(0x20, 0x30); for (let i = 0; i < collect.length; ++i) { - parser.currentState = STATE.DCS_INTERMEDIATE; + parser.currentState = ParserState.DCS_INTERMEDIATE; parser.parse(collect[i]); - chai.expect(parser.currentState).equal(STATE.DCS_INTERMEDIATE); + chai.expect(parser.currentState).equal(ParserState.DCS_INTERMEDIATE); chai.expect(parser.collected).equal(collect[i]); parser.reset(); } @@ -819,9 +819,9 @@ describe('EscapeSequenceParser', function(): void { parser.reset(); let chars = r(0x30, 0x40); for (let i = 0; i < chars.length; ++i) { - parser.currentState = STATE.DCS_INTERMEDIATE; + parser.currentState = ParserState.DCS_INTERMEDIATE; parser.parse('\x20' + chars[i]); - chai.expect(parser.currentState).equal(STATE.DCS_IGNORE); + chai.expect(parser.currentState).equal(ParserState.DCS_IGNORE); chai.expect(parser.collected).equal('\x20'); parser.reset(); } @@ -831,9 +831,9 @@ describe('EscapeSequenceParser', function(): void { testTerminal.clear(); let collect = r(0x40, 0x7f); for (let i = 0; i < collect.length; ++i) { - parser.currentState = STATE.DCS_ENTRY; + parser.currentState = ParserState.DCS_ENTRY; parser.parse(collect[i]); - chai.expect(parser.currentState).equal(STATE.DCS_PASSTHROUGH); + chai.expect(parser.currentState).equal(ParserState.DCS_PASSTHROUGH); testTerminal.compare([['dcs hook', '', [0], collect[i]]]); parser.reset(); testTerminal.clear(); @@ -844,9 +844,9 @@ describe('EscapeSequenceParser', function(): void { testTerminal.clear(); let collect = r(0x40, 0x7f); for (let i = 0; i < collect.length; ++i) { - parser.currentState = STATE.DCS_PARAM; + parser.currentState = ParserState.DCS_PARAM; parser.parse(collect[i]); - chai.expect(parser.currentState).equal(STATE.DCS_PASSTHROUGH); + chai.expect(parser.currentState).equal(ParserState.DCS_PASSTHROUGH); testTerminal.compare([['dcs hook', '', [0], collect[i]]]); parser.reset(); testTerminal.clear(); @@ -857,9 +857,9 @@ describe('EscapeSequenceParser', function(): void { testTerminal.clear(); let collect = r(0x40, 0x7f); for (let i = 0; i < collect.length; ++i) { - parser.currentState = STATE.DCS_INTERMEDIATE; + parser.currentState = ParserState.DCS_INTERMEDIATE; parser.parse(collect[i]); - chai.expect(parser.currentState).equal(STATE.DCS_PASSTHROUGH); + chai.expect(parser.currentState).equal(ParserState.DCS_PASSTHROUGH); testTerminal.compare([['dcs hook', '', [0], collect[i]]]); parser.reset(); testTerminal.clear(); @@ -873,9 +873,9 @@ describe('EscapeSequenceParser', function(): void { puts.concat(r(0x1c, 0x20)); puts.concat(r(0x20, 0x7f)); for (let i = 0; i < puts.length; ++i) { - parser.currentState = STATE.DCS_PASSTHROUGH; + parser.currentState = ParserState.DCS_PASSTHROUGH; parser.parse(puts[i]); - chai.expect(parser.currentState).equal(STATE.DCS_PASSTHROUGH); + chai.expect(parser.currentState).equal(ParserState.DCS_PASSTHROUGH); testTerminal.compare([['dcs put', puts[i]]]); parser.reset(); testTerminal.clear(); @@ -884,9 +884,9 @@ describe('EscapeSequenceParser', function(): void { it('state DCS_PASSTHROUGH ignore', function (): void { parser.reset(); testTerminal.clear(); - parser.currentState = STATE.DCS_PASSTHROUGH; + parser.currentState = ParserState.DCS_PASSTHROUGH; parser.parse('\x7f'); - chai.expect(parser.currentState).equal(STATE.DCS_PASSTHROUGH); + chai.expect(parser.currentState).equal(ParserState.DCS_PASSTHROUGH); testTerminal.compare([]); parser.reset(); testTerminal.clear(); @@ -968,9 +968,9 @@ describe('EscapeSequenceParser', function(): void { it('CSI_IGNORE error', function (): void { parser.reset(); testTerminal.clear(); - parser.currentState = STATE.CSI_IGNORE; + parser.currentState = ParserState.CSI_IGNORE; parser.parse('€öäü'); - chai.expect(parser.currentState).equal(STATE.CSI_IGNORE); + chai.expect(parser.currentState).equal(ParserState.CSI_IGNORE); testTerminal.compare([]); parser.reset(); testTerminal.clear(); @@ -978,9 +978,9 @@ describe('EscapeSequenceParser', function(): void { it('DCS_IGNORE error', function (): void { parser.reset(); testTerminal.clear(); - parser.currentState = STATE.DCS_IGNORE; + parser.currentState = ParserState.DCS_IGNORE; parser.parse('€öäü'); - chai.expect(parser.currentState).equal(STATE.DCS_IGNORE); + chai.expect(parser.currentState).equal(ParserState.DCS_IGNORE); testTerminal.compare([]); parser.reset(); testTerminal.clear(); @@ -988,9 +988,9 @@ describe('EscapeSequenceParser', function(): void { it('DCS_PASSTHROUGH error', function (): void { parser.reset(); testTerminal.clear(); - parser.currentState = STATE.DCS_PASSTHROUGH; + parser.currentState = ParserState.DCS_PASSTHROUGH; parser.parse('€öäü'); - chai.expect(parser.currentState).equal(STATE.DCS_PASSTHROUGH); + chai.expect(parser.currentState).equal(ParserState.DCS_PASSTHROUGH); testTerminal.compare([['dcs put', '€öäü']]); parser.reset(); testTerminal.clear(); @@ -998,9 +998,9 @@ describe('EscapeSequenceParser', function(): void { it('error else of if (code > 159)', function (): void { parser.reset(); testTerminal.clear(); - parser.currentState = STATE.GROUND; + parser.currentState = ParserState.GROUND; parser.parse('\x1e'); - chai.expect(parser.currentState).equal(STATE.GROUND); + chai.expect(parser.currentState).equal(ParserState.GROUND); testTerminal.compare([]); parser.reset(); testTerminal.clear(); @@ -1029,13 +1029,13 @@ describe('EscapeSequenceParser', function(): void { errParser1.parse('\x1b[<31;5€normal print'); errTerminal1.compare([ ['error', { - pos: 7, + position: 7, code: '€'.charCodeAt(0), - state: 4, + currentState: 4, print: -1, dcs: -1, osc: '', - collect: '<', + collected: '<', params: [31, 5]}], ['print', 'normal print'] ]); @@ -1046,13 +1046,13 @@ describe('EscapeSequenceParser', function(): void { errParser2.parse('\x1b[<31;5€no print'); errTerminal2.compare([ ['error', { - pos: 7, + position: 7, code: '€'.charCodeAt(0), - state: 4, + currentState: 4, print: -1, dcs: -1, osc: '', - collect: '<', + collected: '<', params: [31, 5]}] ]); parser.reset(); diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts index 8d7a53bc17..ebe8414c7e 100644 --- a/src/EscapeSequenceParser.ts +++ b/src/EscapeSequenceParser.ts @@ -18,40 +18,40 @@ export interface IParserTerminal { // FSM states -export const enum STATE { +export const enum ParserState { GROUND = 0, - ESCAPE, - ESCAPE_INTERMEDIATE, - CSI_ENTRY, - CSI_PARAM, - CSI_INTERMEDIATE, - CSI_IGNORE, - SOS_PM_APC_STRING, - OSC_STRING, - DCS_ENTRY, - DCS_PARAM, - DCS_IGNORE, - DCS_INTERMEDIATE, - DCS_PASSTHROUGH + 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 ACTION { - ignore = 0, - error, - print, - execute, - osc_start, - osc_put, - osc_end, - csi_dispatch, - param, - collect, - esc_dispatch, - clear, - dcs_hook, - dcs_put, - dcs_unhook +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 } @@ -94,14 +94,14 @@ EXECUTABLES.push(0x19); EXECUTABLES.concat(r(0x1c, 0x20)); // default transition of the FSM is [error, GROUND] -let DEFAULT_TRANSITION = ACTION.error << 4 | STATE.GROUND; +let DEFAULT_TRANSITION = ParserAction.ERROR << 4 | ParserState.GROUND; // default DEC/ANSI compatible state transition table // as defined by https://vt100.net/emu/dec_ansi_parser export const VT500_TRANSITION_TABLE = (function (): TransitionTable { let table: TransitionTable = new TransitionTable(4095); - let states: number[] = r(STATE.GROUND, STATE.DCS_PASSTHROUGH + 1); + let states: number[] = r(ParserState.GROUND, ParserState.DCS_PASSTHROUGH + 1); let state: any; // table with default transition [any] --> [error, GROUND] @@ -115,103 +115,103 @@ export const VT500_TRANSITION_TABLE = (function (): TransitionTable { // apply transitions // printables - table.addMany(PRINTABLES, STATE.GROUND, ACTION.print, STATE.GROUND); + table.addMany(PRINTABLES, ParserState.GROUND, ParserAction.PRINT, ParserState.GROUND); // global anywhere rules for (state in states) { - table.addMany([0x18, 0x1a, 0x99, 0x9a], state, ACTION.execute, STATE.GROUND); - table.addMany(r(0x80, 0x90), state, ACTION.execute, STATE.GROUND); - table.addMany(r(0x90, 0x98), state, ACTION.execute, STATE.GROUND); - table.add(0x9c, state, ACTION.ignore, STATE.GROUND); // ST as terminator - table.add(0x1b, state, ACTION.clear, STATE.ESCAPE); // ESC - table.add(0x9d, state, ACTION.osc_start, STATE.OSC_STRING); // OSC - table.addMany([0x98, 0x9e, 0x9f], state, ACTION.ignore, STATE.SOS_PM_APC_STRING); - table.add(0x9b, state, ACTION.clear, STATE.CSI_ENTRY); // CSI - table.add(0x90, state, ACTION.clear, STATE.DCS_ENTRY); // DCS + 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, STATE.GROUND, ACTION.execute, STATE.GROUND); - table.addMany(EXECUTABLES, STATE.ESCAPE, ACTION.execute, STATE.ESCAPE); - table.add(0x7f, STATE.ESCAPE, ACTION.ignore, STATE.ESCAPE); - table.addMany(EXECUTABLES, STATE.OSC_STRING, ACTION.ignore, STATE.OSC_STRING); - table.addMany(EXECUTABLES, STATE.CSI_ENTRY, ACTION.execute, STATE.CSI_ENTRY); - table.add(0x7f, STATE.CSI_ENTRY, ACTION.ignore, STATE.CSI_ENTRY); - table.addMany(EXECUTABLES, STATE.CSI_PARAM, ACTION.execute, STATE.CSI_PARAM); - table.add(0x7f, STATE.CSI_PARAM, ACTION.ignore, STATE.CSI_PARAM); - table.addMany(EXECUTABLES, STATE.CSI_IGNORE, ACTION.execute, STATE.CSI_IGNORE); - table.addMany(EXECUTABLES, STATE.CSI_INTERMEDIATE, ACTION.execute, STATE.CSI_INTERMEDIATE); - table.add(0x7f, STATE.CSI_INTERMEDIATE, ACTION.ignore, STATE.CSI_INTERMEDIATE); - table.addMany(EXECUTABLES, STATE.ESCAPE_INTERMEDIATE, ACTION.execute, STATE.ESCAPE_INTERMEDIATE); - table.add(0x7f, STATE.ESCAPE_INTERMEDIATE, ACTION.ignore, STATE.ESCAPE_INTERMEDIATE); + 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, STATE.ESCAPE, ACTION.osc_start, STATE.OSC_STRING); - table.addMany(PRINTABLES, STATE.OSC_STRING, ACTION.osc_put, STATE.OSC_STRING); - table.add(0x7f, STATE.OSC_STRING, ACTION.osc_put, STATE.OSC_STRING); - table.addMany([0x9c, 0x1b, 0x18, 0x1a, 0x07], STATE.OSC_STRING, ACTION.osc_end, STATE.GROUND); - table.addMany(r(0x1c, 0x20), STATE.OSC_STRING, ACTION.ignore, STATE.OSC_STRING); + 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], STATE.ESCAPE, ACTION.ignore, STATE.SOS_PM_APC_STRING); - table.addMany(PRINTABLES, STATE.SOS_PM_APC_STRING, ACTION.ignore, STATE.SOS_PM_APC_STRING); - table.addMany(EXECUTABLES, STATE.SOS_PM_APC_STRING, ACTION.ignore, STATE.SOS_PM_APC_STRING); - table.add(0x9c, STATE.SOS_PM_APC_STRING, ACTION.ignore, STATE.GROUND); + 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, STATE.ESCAPE, ACTION.clear, STATE.CSI_ENTRY); - table.addMany(r(0x40, 0x7f), STATE.CSI_ENTRY, ACTION.csi_dispatch, STATE.GROUND); - table.addMany(r(0x30, 0x3a), STATE.CSI_ENTRY, ACTION.param, STATE.CSI_PARAM); - table.add(0x3b, STATE.CSI_ENTRY, ACTION.param, STATE.CSI_PARAM); - table.addMany([0x3c, 0x3d, 0x3e, 0x3f], STATE.CSI_ENTRY, ACTION.collect, STATE.CSI_PARAM); - table.addMany(r(0x30, 0x3a), STATE.CSI_PARAM, ACTION.param, STATE.CSI_PARAM); - table.add(0x3b, STATE.CSI_PARAM, ACTION.param, STATE.CSI_PARAM); - table.addMany(r(0x40, 0x7f), STATE.CSI_PARAM, ACTION.csi_dispatch, STATE.GROUND); - table.addMany([0x3a, 0x3c, 0x3d, 0x3e, 0x3f], STATE.CSI_PARAM, ACTION.ignore, STATE.CSI_IGNORE); - table.addMany(r(0x20, 0x40), STATE.CSI_IGNORE, null, STATE.CSI_IGNORE); - table.add(0x7f, STATE.CSI_IGNORE, null, STATE.CSI_IGNORE); - table.addMany(r(0x40, 0x7f), STATE.CSI_IGNORE, ACTION.ignore, STATE.GROUND); - table.add(0x3a, STATE.CSI_ENTRY, ACTION.ignore, STATE.CSI_IGNORE); - table.addMany(r(0x20, 0x30), STATE.CSI_ENTRY, ACTION.collect, STATE.CSI_INTERMEDIATE); - table.addMany(r(0x20, 0x30), STATE.CSI_INTERMEDIATE, ACTION.collect, STATE.CSI_INTERMEDIATE); - table.addMany(r(0x30, 0x40), STATE.CSI_INTERMEDIATE, ACTION.ignore, STATE.CSI_IGNORE); - table.addMany(r(0x40, 0x7f), STATE.CSI_INTERMEDIATE, ACTION.csi_dispatch, STATE.GROUND); - table.addMany(r(0x20, 0x30), STATE.CSI_PARAM, ACTION.collect, STATE.CSI_INTERMEDIATE); + 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), STATE.ESCAPE, ACTION.collect, STATE.ESCAPE_INTERMEDIATE); - table.addMany(r(0x20, 0x30), STATE.ESCAPE_INTERMEDIATE, ACTION.collect, STATE.ESCAPE_INTERMEDIATE); - table.addMany(r(0x30, 0x7f), STATE.ESCAPE_INTERMEDIATE, ACTION.esc_dispatch, STATE.GROUND); - table.addMany(r(0x30, 0x50), STATE.ESCAPE, ACTION.esc_dispatch, STATE.GROUND); - table.addMany(r(0x51, 0x58), STATE.ESCAPE, ACTION.esc_dispatch, STATE.GROUND); - table.addMany([0x59, 0x5a, 0x5c], STATE.ESCAPE, ACTION.esc_dispatch, STATE.GROUND); - table.addMany(r(0x60, 0x7f), STATE.ESCAPE, ACTION.esc_dispatch, STATE.GROUND); + 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, STATE.ESCAPE, ACTION.clear, STATE.DCS_ENTRY); - table.addMany(EXECUTABLES, STATE.DCS_ENTRY, ACTION.ignore, STATE.DCS_ENTRY); - table.add(0x7f, STATE.DCS_ENTRY, ACTION.ignore, STATE.DCS_ENTRY); - table.addMany(r(0x1c, 0x20), STATE.DCS_ENTRY, ACTION.ignore, STATE.DCS_ENTRY); - table.addMany(r(0x20, 0x30), STATE.DCS_ENTRY, ACTION.collect, STATE.DCS_INTERMEDIATE); - table.add(0x3a, STATE.DCS_ENTRY, ACTION.ignore, STATE.DCS_IGNORE); - table.addMany(r(0x30, 0x3a), STATE.DCS_ENTRY, ACTION.param, STATE.DCS_PARAM); - table.add(0x3b, STATE.DCS_ENTRY, ACTION.param, STATE.DCS_PARAM); - table.addMany([0x3c, 0x3d, 0x3e, 0x3f], STATE.DCS_ENTRY, ACTION.collect, STATE.DCS_PARAM); - table.addMany(EXECUTABLES, STATE.DCS_IGNORE, ACTION.ignore, STATE.DCS_IGNORE); - table.addMany(r(0x20, 0x80), STATE.DCS_IGNORE, ACTION.ignore, STATE.DCS_IGNORE); - table.addMany(r(0x1c, 0x20), STATE.DCS_IGNORE, ACTION.ignore, STATE.DCS_IGNORE); - table.addMany(EXECUTABLES, STATE.DCS_PARAM, ACTION.ignore, STATE.DCS_PARAM); - table.add(0x7f, STATE.DCS_PARAM, ACTION.ignore, STATE.DCS_PARAM); - table.addMany(r(0x1c, 0x20), STATE.DCS_PARAM, ACTION.ignore, STATE.DCS_PARAM); - table.addMany(r(0x30, 0x3a), STATE.DCS_PARAM, ACTION.param, STATE.DCS_PARAM); - table.add(0x3b, STATE.DCS_PARAM, ACTION.param, STATE.DCS_PARAM); - table.addMany([0x3a, 0x3c, 0x3d, 0x3e, 0x3f], STATE.DCS_PARAM, ACTION.ignore, STATE.DCS_IGNORE); - table.addMany(r(0x20, 0x30), STATE.DCS_PARAM, ACTION.collect, STATE.DCS_INTERMEDIATE); - table.addMany(EXECUTABLES, STATE.DCS_INTERMEDIATE, ACTION.ignore, STATE.DCS_INTERMEDIATE); - table.add(0x7f, STATE.DCS_INTERMEDIATE, ACTION.ignore, STATE.DCS_INTERMEDIATE); - table.addMany(r(0x1c, 0x20), STATE.DCS_INTERMEDIATE, ACTION.ignore, STATE.DCS_INTERMEDIATE); - table.addMany(r(0x20, 0x30), STATE.DCS_INTERMEDIATE, ACTION.collect, STATE.DCS_INTERMEDIATE); - table.addMany(r(0x30, 0x40), STATE.DCS_INTERMEDIATE, ACTION.ignore, STATE.DCS_IGNORE); - table.addMany(r(0x40, 0x7f), STATE.DCS_INTERMEDIATE, ACTION.dcs_hook, STATE.DCS_PASSTHROUGH); - table.addMany(r(0x40, 0x7f), STATE.DCS_PARAM, ACTION.dcs_hook, STATE.DCS_PASSTHROUGH); - table.addMany(r(0x40, 0x7f), STATE.DCS_ENTRY, ACTION.dcs_hook, STATE.DCS_PASSTHROUGH); - table.addMany(EXECUTABLES, STATE.DCS_PASSTHROUGH, ACTION.dcs_put, STATE.DCS_PASSTHROUGH); - table.addMany(PRINTABLES, STATE.DCS_PASSTHROUGH, ACTION.dcs_put, STATE.DCS_PASSTHROUGH); - table.add(0x7f, STATE.DCS_PASSTHROUGH, ACTION.ignore, STATE.DCS_PASSTHROUGH); - table.addMany([0x1b, 0x9c], STATE.DCS_PASSTHROUGH, ACTION.dcs_unhook, STATE.GROUND); + 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; })(); @@ -231,7 +231,7 @@ export class EscapeSequenceParser { terminal?: IParserTerminal | any, transitions: TransitionTable = VT500_TRANSITION_TABLE) { - this.initialState = STATE.GROUND; + this.initialState = ParserState.GROUND; this.currentState = this.initialState; this.transitions = transitions; this.osc = ''; @@ -262,7 +262,7 @@ export class EscapeSequenceParser { let currentState = this.currentState; // local buffers - let printed = -1; + let print = -1; let dcs = -1; let osc = this.osc; let collected = this.collected; @@ -275,13 +275,13 @@ export class EscapeSequenceParser { code = s.charCodeAt(i); // shortcut for most chars (print action) - if (currentState === STATE.GROUND && (code > 0x1f && code < 0x80)) { - printed = (~printed) ? printed : i; + if (currentState === ParserState.GROUND && (code > 0x1f && code < 0x80)) { + print = (~print) ? print : i; continue; } // shortcut for CSI params - if (currentState === STATE.CSI_PARAM && (code > 0x2f && code < 0x39)) { + if (currentState === ParserState.CSI_PARAM && (code > 0x2f && code < 0x39)) { params[params.length - 1] = params[params.length - 1] * 10 + code - 48; continue; } @@ -289,47 +289,47 @@ export class EscapeSequenceParser { // normal transition & action lookup transition = (code < 0xa0) ? (table[currentState << 8 | code]) : DEFAULT_TRANSITION; switch (transition >> 4) { - case ACTION.print: - printed = (~printed) ? printed : i; + case ParserAction.PRINT: + print = (~print) ? print : i; break; - case ACTION.execute: - if (~printed) { - this.term.actionPrint(s, printed, i); - printed = -1; + case ParserAction.EXECUTE: + if (~print) { + this.term.actionPrint(s, print, i); + print = -1; } this.term.actionExecute(String.fromCharCode(code)); break; - case ACTION.ignore: + case ParserAction.IGNORE: // handle leftover print or dcs chars - if (~printed) { - this.term.actionPrint(s, printed, i); - printed = -1; + if (~print) { + this.term.actionPrint(s, print, i); + print = -1; } else if (~dcs) { this.term.actionDCSPrint(s, dcs, i); dcs = -1; } break; - case ACTION.error: + case ParserAction.ERROR: // chars higher than 0x9f are handled by this action to // keep the lookup table small if (code > 0x9f) { switch (currentState) { - case STATE.GROUND: // add char to print string - printed = (~printed) ? printed : i; + case ParserState.GROUND: // add char to print string + print = (~print) ? print : i; break; - case STATE.OSC_STRING: // add char to osc string + case ParserState.OSC_STRING: // add char to osc string osc += String.fromCharCode(code); - transition |= STATE.OSC_STRING; + transition |= ParserState.OSC_STRING; break; - case STATE.CSI_IGNORE: // ignore char - transition |= STATE.CSI_IGNORE; + case ParserState.CSI_IGNORE: // ignore char + transition |= ParserState.CSI_IGNORE; break; - case STATE.DCS_IGNORE: // ignore char - transition |= STATE.DCS_IGNORE; + case ParserState.DCS_IGNORE: // ignore char + transition |= ParserState.DCS_IGNORE; break; - case STATE.DCS_PASSTHROUGH: // add char to dcs string + case ParserState.DCS_PASSTHROUGH: // add char to dcs string dcs = (~dcs) ? dcs : i; - transition |= STATE.DCS_PASSTHROUGH; + transition |= ParserState.DCS_PASSTHROUGH; break; default: error = true; @@ -342,71 +342,71 @@ export class EscapeSequenceParser { if (error) { if (this.term.actionError( { - pos: i, // position in string - code: code, // actual character code - state: currentState, // current state - print: printed, // print buffer start index - dcs: dcs, // dcs buffer start index - osc: osc, // osc string buffer - collect: collected, // collect buffer - params: params // params buffer + position: i, // position in string + code, // actual character code + currentState, // current state + print, // print buffer start index + dcs, // dcs buffer start index + osc, // osc string buffer + collected, // collect buffer + params // params buffer })) { return; } error = false; } break; - case ACTION.csi_dispatch: + case ParserAction.CSI_DISPATCH: this.term.actionCSI(collected, params, String.fromCharCode(code)); break; - case ACTION.param: + case ParserAction.PARAM: if (code === 0x3b) params.push(0); else params[params.length - 1] = params[params.length - 1] * 10 + code - 48; break; - case ACTION.collect: + case ParserAction.COLLECT: collected += String.fromCharCode(code); break; - case ACTION.esc_dispatch: + case ParserAction.ESC_DISPATCH: this.term.actionESC(collected, String.fromCharCode(code)); break; - case ACTION.clear: - if (~printed) { - this.term.actionPrint(s, printed, i); - printed = -1; + case ParserAction.CLEAR: + if (~print) { + this.term.actionPrint(s, print, i); + print = -1; } osc = ''; params = [0]; collected = ''; dcs = -1; break; - case ACTION.dcs_hook: + case ParserAction.DCS_HOOK: this.term.actionDCSHook(collected, params, String.fromCharCode(code)); break; - case ACTION.dcs_put: + case ParserAction.DCS_PUT: dcs = (~dcs) ? dcs : i; break; - case ACTION.dcs_unhook: + case ParserAction.DCS_UNHOOK: if (~dcs) this.term.actionDCSPrint(s, dcs, i); this.term.actionDCSUnhook(); - if (code === 0x1b) transition |= STATE.ESCAPE; + if (code === 0x1b) transition |= ParserState.ESCAPE; osc = ''; params = [0]; collected = ''; dcs = -1; break; - case ACTION.osc_start: - if (~printed) { - this.term.actionPrint(s, printed, i); - printed = -1; + case ParserAction.OSC_START: + if (~print) { + this.term.actionPrint(s, print, i); + print = -1; } osc = ''; break; - case ACTION.osc_put: + case ParserAction.OSC_PUT: osc += s.charAt(i); break; - case ACTION.osc_end: + case ParserAction.OSC_END: if (osc && code !== 0x18 && code !== 0x1a) this.term.actionOSC(osc); - if (code === 0x1b) transition |= STATE.ESCAPE; + if (code === 0x1b) transition |= ParserState.ESCAPE; osc = ''; params = [0]; collected = ''; @@ -417,9 +417,9 @@ export class EscapeSequenceParser { } // push leftover pushable buffers to terminal - if (currentState === STATE.GROUND && ~printed) { - this.term.actionPrint(s, printed, s.length); - } else if (currentState === STATE.DCS_PASSTHROUGH && ~dcs) { + if (currentState === ParserState.GROUND && ~print) { + this.term.actionPrint(s, print, s.length); + } else if (currentState === ParserState.DCS_PASSTHROUGH && ~dcs) { this.term.actionDCSPrint(s, dcs, s.length); } From 9eb107a6e9108c777307847e81138176ffd561b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 24 Apr 2018 18:53:19 +0200 Subject: [PATCH 08/44] first handler API changes --- src/EscapeSequenceParser.test.ts | 7 + src/EscapeSequenceParser.ts | 250 +++++++++++++++++++++++-------- 2 files changed, 192 insertions(+), 65 deletions(-) diff --git a/src/EscapeSequenceParser.test.ts b/src/EscapeSequenceParser.test.ts index 50d8d5516c..7651803db1 100644 --- a/src/EscapeSequenceParser.test.ts +++ b/src/EscapeSequenceParser.test.ts @@ -69,6 +69,7 @@ let states: number[] = [ let state: any; let parser = new EscapeSequenceParser(testTerminal); +parser.registerPrintHandler(testTerminal.actionPrint.bind(testTerminal)); describe('EscapeSequenceParser', function(): void { @@ -1007,6 +1008,7 @@ describe('EscapeSequenceParser', function(): void { }); }); + /* let errorTerminal1 = function(): void {}; errorTerminal1.prototype = testTerminal; let errTerminal1 = new errorTerminal1(); @@ -1014,6 +1016,7 @@ describe('EscapeSequenceParser', function(): void { this.calls.push(['error', e]); }; let errParser1 = new EscapeSequenceParser(errTerminal1); + errParser1.registerPrintHandler(testTerminal.actionPrint.bind(errorTerminal1)); let errorTerminal2 = function(): void {}; errorTerminal2.prototype = testTerminal; @@ -1023,12 +1026,14 @@ describe('EscapeSequenceParser', function(): void { 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, @@ -1046,6 +1051,7 @@ describe('EscapeSequenceParser', function(): void { errParser2.parse('\x1b[<31;5€no print'); errTerminal2.compare([ ['error', { + abort: false, position: 7, code: '€'.charCodeAt(0), currentState: 4, @@ -1059,5 +1065,6 @@ describe('EscapeSequenceParser', function(): void { testTerminal.clear(); }); }); + */ }); diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts index ebe8414c7e..592c548b4b 100644 --- a/src/EscapeSequenceParser.ts +++ b/src/EscapeSequenceParser.ts @@ -16,6 +16,43 @@ export interface IParserTerminal { actionError?: () => void; // FIXME: real signature and error handling } +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 + collected: string; // collect buffer + params: number[]; // params buffer + abort: boolean; // should abort (default: false) +} + +export interface IDcsHandler { + hook(collect: string): void; + put(data: string): void; + unhook(): void; +} + +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; + 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; + deregisterErrorHandler(callback: (state: IParsingState) => IParsingState): void; +} + // FSM states export const enum ParserState { @@ -219,7 +256,7 @@ export const VT500_TRANSITION_TABLE = (function (): TransitionTable { // default transition table points to global object // Q: Copy table to allow custom sequences w'o changing global object? -export class EscapeSequenceParser { +export class EscapeSequenceParser implements IEscapeSequenceParser { public initialState: number; public currentState: number; public transitions: TransitionTable; @@ -227,6 +264,10 @@ export class EscapeSequenceParser { public params: number[]; public collected: string; public term: any; + + private _printHandler: (data: string, start: number, end: number) => void; + private _csiHandlers: Function[]; + constructor( terminal?: IParserTerminal | any, transitions: TransitionTable = VT500_TRANSITION_TABLE) @@ -246,7 +287,31 @@ export class EscapeSequenceParser { this.term[instructions[i]] = function(): void {}; } } + this._printHandler = function(): void {}; + + this._csiHandlers = []; + for (let i=0; i<256; ++i) + this._csiHandlers.push(function(): void {}); + } + + registerPrintHandler(callback: (data: string, start: number, end: number) => void): 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 {} + deregisterErrorHandler(callback: (state: IParsingState) => IParsingState): void {} reset(): void { this.currentState = this.initialState; @@ -255,7 +320,7 @@ export class EscapeSequenceParser { this.collected = ''; } - parse(s: string): void { + parse(data: string): void { let code = 0; let transition = 0; let error = false; @@ -270,9 +335,9 @@ export class EscapeSequenceParser { let table: Uint8Array = this.transitions.table; // process input string - let l = s.length; + let l = data.length; for (let i = 0; i < l; ++i) { - code = s.charCodeAt(i); + code = data.charCodeAt(i); // shortcut for most chars (print action) if (currentState === ParserState.GROUND && (code > 0x1f && code < 0x80)) { @@ -294,7 +359,7 @@ export class EscapeSequenceParser { break; case ParserAction.EXECUTE: if (~print) { - this.term.actionPrint(s, print, i); + this._printHandler(data, print, i); print = -1; } this.term.actionExecute(String.fromCharCode(code)); @@ -302,10 +367,10 @@ export class EscapeSequenceParser { case ParserAction.IGNORE: // handle leftover print or dcs chars if (~print) { - this.term.actionPrint(s, print, i); + this._printHandler(data, print, i); print = -1; } else if (~dcs) { - this.term.actionDCSPrint(s, dcs, i); + this.term.actionDCSPrint(data, dcs, i); dcs = -1; } break; @@ -345,11 +410,12 @@ export class EscapeSequenceParser { position: i, // position in string code, // actual character code currentState, // current state - print, // print buffer start index + print, // print buffer start index dcs, // dcs buffer start index osc, // osc string buffer collected, // collect buffer - params // params buffer + params, // params buffer + abort: false // abort flag })) { return; } @@ -357,7 +423,8 @@ export class EscapeSequenceParser { } break; case ParserAction.CSI_DISPATCH: - this.term.actionCSI(collected, params, String.fromCharCode(code)); + // this.term.actionCSI(collected, params, String.fromCharCode(code)); + this._csiHandlers[code](params, collected); break; case ParserAction.PARAM: if (code === 0x3b) params.push(0); @@ -371,7 +438,7 @@ export class EscapeSequenceParser { break; case ParserAction.CLEAR: if (~print) { - this.term.actionPrint(s, print, i); + this._printHandler(data, print, i); print = -1; } osc = ''; @@ -386,7 +453,7 @@ export class EscapeSequenceParser { dcs = (~dcs) ? dcs : i; break; case ParserAction.DCS_UNHOOK: - if (~dcs) this.term.actionDCSPrint(s, dcs, i); + if (~dcs) this.term.actionDCSPrint(data, dcs, i); this.term.actionDCSUnhook(); if (code === 0x1b) transition |= ParserState.ESCAPE; osc = ''; @@ -396,13 +463,13 @@ export class EscapeSequenceParser { break; case ParserAction.OSC_START: if (~print) { - this.term.actionPrint(s, print, i); + this._printHandler(data, print, i); print = -1; } osc = ''; break; case ParserAction.OSC_PUT: - osc += s.charAt(i); + osc += data.charAt(i); break; case ParserAction.OSC_END: if (osc && code !== 0x18 && code !== 0x1a) this.term.actionOSC(osc); @@ -418,9 +485,9 @@ export class EscapeSequenceParser { // push leftover pushable buffers to terminal if (currentState === ParserState.GROUND && ~print) { - this.term.actionPrint(s, print, s.length); + this._printHandler(data, print, data.length); } else if (currentState === ParserState.DCS_PASSTHROUGH && ~dcs) { - this.term.actionDCSPrint(s, dcs, s.length); + this.term.actionDCSPrint(data, dcs, data.length); } // save non pushable buffers @@ -448,6 +515,59 @@ export class ParserTerminal implements IParserTerminal { this._parser = new EscapeSequenceParser(this); 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), + (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), + (params, collect) => { + if (collect === '!') { + return this._inputHandler.softReset(params); + } + }); + this._parser.registerCsiHandler('q'.charCodeAt(0), + (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)); } parse(data: string): void { @@ -669,55 +789,55 @@ export class ParserTerminal implements IParserTerminal { 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); + // 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); } From b72cdadd113c0fd13bde24489304011bd840d602 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 29 Apr 2018 15:48:19 +0200 Subject: [PATCH 09/44] move switches to jump tables --- src/EscapeSequenceParser.test.ts_disabled | 1070 +++++++++++++++++++++ src/EscapeSequenceParser.ts | 614 +++++------- 2 files changed, 1317 insertions(+), 367 deletions(-) create mode 100644 src/EscapeSequenceParser.test.ts_disabled diff --git a/src/EscapeSequenceParser.test.ts_disabled b/src/EscapeSequenceParser.test.ts_disabled new file mode 100644 index 0000000000..fe62629039 --- /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 592c548b4b..59aff250b5 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 - } } From 9d2bbd199c9bd625d043b6d44e8a4f3a592d049c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 29 Apr 2018 17:23:38 +0200 Subject: [PATCH 10/44] remove term from EscapeSequenceParser --- src/EscapeSequenceParser.test.ts | 1070 ------------------------------ src/EscapeSequenceParser.ts | 190 +++--- 2 files changed, 112 insertions(+), 1148 deletions(-) delete mode 100644 src/EscapeSequenceParser.test.ts diff --git a/src/EscapeSequenceParser.test.ts b/src/EscapeSequenceParser.test.ts deleted file mode 100644 index 7651803db1..0000000000 --- a/src/EscapeSequenceParser.test.ts +++ /dev/null @@ -1,1070 +0,0 @@ -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 - }, - actionPrint: 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.actionPrint.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.actionPrint); - 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 59aff250b5..3e2bba3bd0 100644 --- a/src/EscapeSequenceParser.ts +++ b/src/EscapeSequenceParser.ts @@ -23,7 +23,7 @@ export interface IParsingState { print: number; // print buffer start index dcs: number; // dcs buffer start index osc: string; // osc string buffer - collected: string; // collect buffer + collect: string; // collect buffer params: number[]; // params buffer abort: boolean; // should abort (default: false) } @@ -41,35 +41,39 @@ export interface ICsiHandler { } export interface IDcsHandler { - hook(collect: string): void; - put(data: string): void; + hook(collect: string, params: number[], flag: string): 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; registerPrintHandler(callback: IPrintHandler): void; - deregisterPrintHandler(callback: (data: string) => void): void; + deregisterPrintHandler(): void; registerExecuteHandler(flag: string, callback: IExecuteHandler): void; - deregisterExecuteHandler(flag: string, callback: () => void): void; + deregisterExecuteHandler(flag: string): void; registerCsiHandler(flag: string, callback: ICsiHandler): void; - deregisterCsiHandler(flag: string, callback: (params: number[], collect: string) => void): void; + deregisterCsiHandler(flag: string): void; registerEscHandler(collect: string, flag: string, callback: (flag: string) => void): void; - deregisterEscHandler(collect: string, flag: string, callback: (collect: string) => void): void; + deregisterEscHandler(collect: string, flag: string): void; registerOscHandler(ident: number, callback: (data: string) => void): void; - deregisterOscHandler(ident: number, callback: (data: string) => void): void; + deregisterOscHandler(ident: number): void; registerDcsHandler(collect: string, flag: string, handler: IDcsHandler): void; - deregisterDcsHandler(collect: string, flag: string, handler: IDcsHandler): void; + deregisterDcsHandler(collect: string, flag: string): void; registerErrorHandler(callback: (state: IParsingState) => IParsingState): void; - deregisterErrorHandler(callback: (state: IParsingState) => IParsingState): void; + deregisterErrorHandler(): void; // remove after revamp of InputHandler methods registerPrefixHandler(callback: (collect: string) => void): void; @@ -275,6 +279,12 @@ export const VT500_TRANSITION_TABLE = (function (): TransitionTable { return table; })(); +// dummy handler for DCS, does really nothing +class DcsDummy implements IDcsHandler { + hook(collect: string, params: number[], flag: string): void {} + put(data: string, start: number, end: number): void {} + unhook(): void {} +} // default transition table points to global object // Q: Copy table to allow custom sequences w'o changing global object? @@ -282,82 +292,92 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { public initialState: number; public currentState: number; public transitions: TransitionTable; - public osc: string; - public params: number[]; - public collected: string; - public term: any; - - private _printHandler: IPrintHandler; - private _executeHandlers: any; - private _csiHandlers: any; - private _escHandlers: any; - private _oscHandlers: any; - private _dcsHandlers: any; + + // buffers over several parse calls + protected _osc: string; + protected _params: number[]; + protected _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 _dummyDcsHandler: IDcsHandler; + protected _errorHandler: IErrorHandler; // FIXME: to be removed - private _tempPrefixHandler: any; + protected _tempPrefixHandler: any; - constructor( - terminal?: IParserTerminal | any, - transitions: TransitionTable = VT500_TRANSITION_TABLE) - { + constructor(transitions: TransitionTable = VT500_TRANSITION_TABLE) { this.initialState = ParserState.GROUND; this.currentState = this.initialState; this.transitions = transitions; - this.osc = ''; - this.params = [0]; - this.collected = ''; - this.term = terminal || {}; - let instructions = [ - 'actionPrint', 'actionOSC', 'actionExecute', 'actionCSI', 'actionESC', - 'actionDCSHook', 'actionDCSPrint', 'actionDCSUnhook', 'actionError']; - for (let i = 0; i < instructions.length; ++i) { - if (!(instructions[i] in this.term)) { - this.term[instructions[i]] = function(): void {}; - } - } + this._osc = ''; + this._params = [0]; + this._collect = ''; 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); + this._activeDcsHandler = null; + this._dummyDcsHandler = new DcsDummy; + this._errorHandler = (state: IParsingState): IParsingState => state; } registerPrintHandler(callback: IPrintHandler): void { this._printHandler = callback; } - deregisterPrintHandler(callback: (data: string) => void): void {} + deregisterPrintHandler(): void { + this._printHandler = (data, start, end): void => {}; + } registerExecuteHandler(flag: string, callback: IExecuteHandler): void { this._executeHandlers[flag.charCodeAt(0)] = callback; } - deregisterExecuteHandler(flag: string, callback: () => void): void {} + deregisterExecuteHandler(flag: string): void { + if (this._executeHandlers[flag.charCodeAt(0)]) delete this._executeHandlers[flag.charCodeAt(0)]; + } registerCsiHandler(flag: string, callback: ICsiHandler): void { this._csiHandlers[flag.charCodeAt(0)] = callback; } - deregisterCsiHandler(flag: string, callback: (params: number[], collect: string) => void): void {} + deregisterCsiHandler(flag: string): void { + if (this._csiHandlers[flag.charCodeAt(0)]) delete this._csiHandlers[flag.charCodeAt(0)]; + } 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 {} + deregisterEscHandler(collect: string, flag: string): void { + if (this._escHandlers[collect + flag]) delete this._escHandlers[collect + flag]; + } registerOscHandler(ident: number, callback: (data: string) => void): void { this._oscHandlers[ident] = callback; } - deregisterOscHandler(ident: number, callback: (data: string) => void): void {} + deregisterOscHandler(ident: number): void { + if (this._oscHandlers[ident]) delete this._oscHandlers[ident]; + } 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 { + deregisterDcsHandler(collect: string, flag: string): void { + if (this._dcsHandlers[collect + flag]) delete this._dcsHandlers[collect + flag]; + } + registerErrorHandler(callback: IErrorHandler): void { + this._errorHandler = callback; + } + deregisterErrorHandler(): void { + this._errorHandler = (state: IParsingState): IParsingState => state; } - deregisterErrorHandler(callback: (state: IParsingState) => IParsingState): void {} // FIXME: to be removed registerPrefixHandler(callback: (collect: string) => void): void { @@ -366,9 +386,9 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { reset(): void { this.currentState = this.initialState; - this.osc = ''; - this.params = [0]; - this.collected = ''; + this._osc = ''; + this._params = [0]; + this._collect = ''; } parse(data: string): void { @@ -380,10 +400,12 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { // local buffers let print = -1; let dcs = -1; - let osc = this.osc; - let collected = this.collected; - let params = this.params; + let osc = this._osc; + let collect = this._collect; + let params = this._params; let table: Uint8Array = this.transitions.table; + let dcsHandler: IDcsHandler | null = this._activeDcsHandler; + let ident: string = ''; // process input string let l = data.length; @@ -422,7 +444,7 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { this._printHandler(data, print, i); print = -1; } else if (~dcs) { - this.term.actionDCSPrint(data, dcs, i); + dcsHandler.put(data, dcs, i); dcs = -1; } break; @@ -455,9 +477,8 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { error = true; } // if we end up here a real error happened - // FIXME: eval and inject return values if (error) { - if (this.term.actionError( + let inject: IParsingState = this._errorHandler( { position: i, // position in string code, // actual character code @@ -465,31 +486,31 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { print, // print buffer start index dcs, // dcs buffer start index osc, // osc string buffer - collected, // collect buffer + collect, // collect buffer params, // params buffer abort: false // abort flag - })) { - return; - } + }); + if (inject.abort) return; + // FIXME: inject return values error = false; } break; case ParserAction.CSI_DISPATCH: - 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 + this._tempPrefixHandler(collect); // FIXME: to be removed + if (this._csiHandlers[code]) this._csiHandlers[code](params, collect); + else console.log('unhandled CSI %s %s %s', code, params, collect); // FIXME: set some default action 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: - collected += String.fromCharCode(code); + collect += String.fromCharCode(code); break; case ParserAction.ESC_DISPATCH: - 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 + ident = collect + String.fromCharCode(code); + if (this._escHandlers[ident]) this._escHandlers[ident](params, collect); + else console.log('unhandled ESC %s %s', collect, String.fromCharCode(code)); // FIXME: set some default action break; case ParserAction.CLEAR: if (~print) { @@ -498,22 +519,26 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { } osc = ''; params = [0]; - collected = ''; + collect = ''; dcs = -1; break; case ParserAction.DCS_HOOK: - this.term.actionDCSHook(collected, params, String.fromCharCode(code)); + ident = collect + String.fromCharCode(code); + if (this._dcsHandlers[ident]) dcsHandler = new this._dcsHandlers[ident]; + else dcsHandler = this._dummyDcsHandler; + dcsHandler.hook(collect, params, String.fromCharCode(code)); break; case ParserAction.DCS_PUT: dcs = (~dcs) ? dcs : i; break; case ParserAction.DCS_UNHOOK: - if (~dcs) this.term.actionDCSPrint(data, dcs, i); - this.term.actionDCSUnhook(); + if (~dcs) dcsHandler.put(data, dcs, i); + dcsHandler.unhook(); + dcsHandler = null; if (code === 0x1b) transition |= ParserState.ESCAPE; osc = ''; params = [0]; - collected = ''; + collect = ''; dcs = -1; break; case ParserAction.OSC_START: @@ -537,7 +562,7 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { if (code === 0x1b) transition |= ParserState.ESCAPE; osc = ''; params = [0]; - collected = ''; + collect = ''; dcs = -1; break; } @@ -548,13 +573,16 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { if (currentState === ParserState.GROUND && ~print) { this._printHandler(data, print, data.length); } else if (currentState === ParserState.DCS_PASSTHROUGH && ~dcs) { - this.term.actionDCSPrint(data, dcs, data.length); + dcsHandler.put(data, dcs, data.length); } // save non pushable buffers - this.osc = osc; - this.collected = collected; - this.params = params; + this._osc = osc; + this._collect = collect; + this._params = params; + + // save active dcs handler reference + this._activeDcsHandler = dcsHandler; // save state this.currentState = currentState; @@ -571,7 +599,7 @@ export class ParserTerminal implements IParserTerminal { private _inputHandler: IInputHandler; constructor(_inputHandler: IInputHandler, _terminal: any) { - this._parser = new EscapeSequenceParser(this); + this._parser = new EscapeSequenceParser; this._terminal = _terminal; this._inputHandler = _inputHandler; @@ -733,6 +761,12 @@ export class ParserTerminal implements IParserTerminal { this._parser.registerEscHandler('-', flag, () => this._terminal.setgCharset(1, CHARSETS[flag] || DEFAULT_CHARSET)); this._parser.registerEscHandler('.', flag, () => this._terminal.setgCharset(2, CHARSETS[flag] || DEFAULT_CHARSET)); } + + // error handler + this._parser.registerErrorHandler((state) => { + console.log('parsing error:', state); + return state; + }); } From 2d77fcf2fbcd45b6061389d3e7575a47b4b242a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 29 Apr 2018 19:10:47 +0200 Subject: [PATCH 11/44] merge ParserTerminal with InputHandler - quick'n dirty merge of classes - ParserTerminal is subclass of InputHandler - basic fallback handlers implemented - test regressions due to multiple changes in EscapeSequenceParser - runtime down to 2.3s - TODO: fix tests, implement DCS functions, full merge of classes --- src/EscapeSequenceParser.ts | 326 +++++++++++++++++++++--------------- src/InputHandler.ts | 4 +- src/Terminal.ts | 2 +- 3 files changed, 194 insertions(+), 138 deletions(-) diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts index 3e2bba3bd0..4399428093 100644 --- a/src/EscapeSequenceParser.ts +++ b/src/EscapeSequenceParser.ts @@ -1,6 +1,8 @@ -import { IInputHandler, IInputHandlingTerminal } from './Types'; import { CHARSETS, DEFAULT_CHARSET } from './Charsets'; import { C0 } from './EscapeSequences'; +import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from './Buffer'; +import { wcwidth } from './CharWidth'; +import { InputHandler } from './InputHandler'; // terminal interface for the escape sequence parser @@ -40,8 +42,16 @@ export interface ICsiHandler { (params: number[], collect: string): void; } +export interface IEscHandler { + (flag: string): void; +} + +export interface IOscHandler { + (data: string): void; +} + export interface IDcsHandler { - hook(collect: string, params: number[], flag: string): void; + hook(collect: string, params: number[], flag: number): void; put(data: string, start: number, end: number): void; unhook(): void; } @@ -54,29 +64,34 @@ export interface IEscapeSequenceParser { reset(): void; parse(data: string): void; - registerPrintHandler(callback: IPrintHandler): void; - deregisterPrintHandler(): void; + setPrintHandler(callback: IPrintHandler): void; + clearPrintHandler(): void; - registerExecuteHandler(flag: string, callback: IExecuteHandler): void; - deregisterExecuteHandler(flag: string): void; + setExecuteHandler(flag: string, callback: IExecuteHandler): void; + clearExecuteHandler(flag: string): void; + setExecuteHandlerFallback(callback: (...params: any[]) => void); - registerCsiHandler(flag: string, callback: ICsiHandler): void; - deregisterCsiHandler(flag: string): void; + setCsiHandler(flag: string, callback: ICsiHandler): void; + clearCsiHandler(flag: string): void; + setCsiHandlerFallback(callback: (...params: any[]) => void); - registerEscHandler(collect: string, flag: string, callback: (flag: string) => void): void; - deregisterEscHandler(collect: string, flag: string): void; + setEscHandler(collect: string, flag: string, callback: IEscHandler): void; + clearEscHandler(collect: string, flag: string): void; + setEscHandlerFallback(callback: (...params: any[]) => void); - registerOscHandler(ident: number, callback: (data: string) => void): void; - deregisterOscHandler(ident: number): void; + setOscHandler(ident: number, callback: IOscHandler): void; + clearOscHandler(ident: number): void; + setOscHandlerFallback(callback: (...params: any[]) => void); - registerDcsHandler(collect: string, flag: string, handler: IDcsHandler): void; - deregisterDcsHandler(collect: string, flag: string): void; + setDcsHandler(collect: string, flag: string, handler: IDcsHandler): void; + clearDcsHandler(collect: string, flag: string): void; + setDcsHandlerFallback(handler: IDcsHandler): void; - registerErrorHandler(callback: (state: IParsingState) => IParsingState): void; - deregisterErrorHandler(): void; + setErrorHandler(callback: IErrorHandler): void; + clearErrorHandler(): void; // remove after revamp of InputHandler methods - registerPrefixHandler(callback: (collect: string) => void): void; + setPrefixHandler(callback: (collect: string) => void): void; } @@ -279,9 +294,9 @@ export const VT500_TRANSITION_TABLE = (function (): TransitionTable { return table; })(); -// dummy handler for DCS, does really nothing +// fallback dummy DCS handler, does really nothing class DcsDummy implements IDcsHandler { - hook(collect: string, params: number[], flag: string): void {} + hook(collect: string, params: number[], flag: number): void {} put(data: string, start: number, end: number): void {} unhook(): void {} } @@ -306,9 +321,17 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { protected _oscHandlers: any; protected _dcsHandlers: any; protected _activeDcsHandler: IDcsHandler | null; - protected _dummyDcsHandler: IDcsHandler; 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; + // FIXME: to be removed protected _tempPrefixHandler: any; @@ -319,68 +342,92 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { this._osc = ''; this._params = [0]; this._collect = ''; - this._printHandler = (data, start, end): void => {}; + + // 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._dummyDcsHandler = new DcsDummy; - this._errorHandler = (state: IParsingState): IParsingState => state; + this._errorHandler = this._errorHandlerFb; } - registerPrintHandler(callback: IPrintHandler): void { + setPrintHandler(callback: IPrintHandler): void { this._printHandler = callback; } - deregisterPrintHandler(): void { - this._printHandler = (data, start, end): void => {}; + clearPrintHandler(): void { + this._printHandler = this._printHandlerFb; } - registerExecuteHandler(flag: string, callback: IExecuteHandler): void { + setExecuteHandler(flag: string, callback: IExecuteHandler): void { this._executeHandlers[flag.charCodeAt(0)] = callback; } - deregisterExecuteHandler(flag: string): void { + clearExecuteHandler(flag: string): void { if (this._executeHandlers[flag.charCodeAt(0)]) delete this._executeHandlers[flag.charCodeAt(0)]; } + setExecuteHandlerFallback(callback): void { + this._escHandlerFb = callback; + } - registerCsiHandler(flag: string, callback: ICsiHandler): void { + setCsiHandler(flag: string, callback: ICsiHandler): void { this._csiHandlers[flag.charCodeAt(0)] = callback; } - deregisterCsiHandler(flag: string): void { + clearCsiHandler(flag: string): void { if (this._csiHandlers[flag.charCodeAt(0)]) delete this._csiHandlers[flag.charCodeAt(0)]; } + setCsiHandlerFallback(callback): void { + this._csiHandlerFb = callback; + } - registerEscHandler(collect: string, flag: string, callback: (collect: string) => void): void { + setEscHandler(collect: string, flag: string, callback: IEscHandler): void { this._escHandlers[collect + flag] = callback; } - deregisterEscHandler(collect: string, flag: string): void { + clearEscHandler(collect: string, flag: string): void { if (this._escHandlers[collect + flag]) delete this._escHandlers[collect + flag]; } + setEscHandlerFallback(callback): void { + this._escHandlerFb = callback; + } - registerOscHandler(ident: number, callback: (data: string) => void): void { + setOscHandler(ident: number, callback: IOscHandler): void { this._oscHandlers[ident] = callback; } - deregisterOscHandler(ident: number): void { + clearOscHandler(ident: number): void { if (this._oscHandlers[ident]) delete this._oscHandlers[ident]; } + setOscHandlerFallback(callback): void { + this._oscHandlerFb = callback; + } - registerDcsHandler(collect: string, flag: string, handler: IDcsHandler): void { + setDcsHandler(collect: string, flag: string, handler: IDcsHandler): void { this._dcsHandlers[collect + flag] = handler; } - deregisterDcsHandler(collect: string, flag: string): void { + clearDcsHandler(collect: string, flag: string): void { if (this._dcsHandlers[collect + flag]) delete this._dcsHandlers[collect + flag]; } + setDcsHandlerFallback(handler: IDcsHandler): void { + this._dcsHandlerFb = handler; + } - registerErrorHandler(callback: IErrorHandler): void { + setErrorHandler(callback: IErrorHandler): void { this._errorHandler = callback; } - deregisterErrorHandler(): void { - this._errorHandler = (state: IParsingState): IParsingState => state; + clearErrorHandler(): void { + this._errorHandler = this._errorHandlerFb; } // FIXME: to be removed - registerPrefixHandler(callback: (collect: string) => void): void { + setPrefixHandler(callback: (collect: string) => void): void { this._tempPrefixHandler = callback; } @@ -436,7 +483,7 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { print = -1; } if (this._executeHandlers[code]) this._executeHandlers[code](); - else console.log('unhandled EXEC %s', code); // FIXME: set some default action + else this._executeHandlerFb(code); break; case ParserAction.IGNORE: // handle leftover print or dcs chars @@ -498,7 +545,7 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { case ParserAction.CSI_DISPATCH: this._tempPrefixHandler(collect); // FIXME: to be removed if (this._csiHandlers[code]) this._csiHandlers[code](params, collect); - else console.log('unhandled CSI %s %s %s', code, params, collect); // FIXME: set some default action + else this._csiHandlerFb(collect, params, code); break; case ParserAction.PARAM: if (code === 0x3b) params.push(0); @@ -510,7 +557,7 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { case ParserAction.ESC_DISPATCH: ident = collect + String.fromCharCode(code); if (this._escHandlers[ident]) this._escHandlers[ident](params, collect); - else console.log('unhandled ESC %s %s', collect, String.fromCharCode(code)); // FIXME: set some default action + else this._escHandlerFb(collect, code); break; case ParserAction.CLEAR: if (~print) { @@ -524,9 +571,9 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { break; case ParserAction.DCS_HOOK: ident = collect + String.fromCharCode(code); - if (this._dcsHandlers[ident]) dcsHandler = new this._dcsHandlers[ident]; - else dcsHandler = this._dummyDcsHandler; - dcsHandler.hook(collect, params, 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; @@ -557,7 +604,7 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { 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 + else this._oscHandlerFb(identifier, content); } if (code === 0x1b) transition |= ParserState.ESCAPE; osc = ''; @@ -589,98 +636,109 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { } } -import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from './Buffer'; -import { wcwidth } from './CharWidth'; -// Q: Merge class with InputHandler? -export class ParserTerminal implements IParserTerminal { +export class ParserTerminal extends InputHandler { private _parser: EscapeSequenceParser; - private _terminal: any; - private _inputHandler: IInputHandler; - constructor(_inputHandler: IInputHandler, _terminal: any) { + constructor(_terminal: any) { + super(_terminal); this._parser = new EscapeSequenceParser; - this._terminal = _terminal; - this._inputHandler = _inputHandler; + + // custom fallback handlers + this._parser.setCsiHandlerFallback((...params: any[]) => { + this._terminal.error('Unknown CSI code: ', params); + }); + + this._parser.setEscHandlerFallback((...params: any[]) => { + this._terminal.error('Unknown ESC code: ', params); + }); + + this._parser.setExecuteHandlerFallback((...params: any[]) => { + this._terminal.error('Unknown EXECUTE code: ', params); + }); + + this._parser.setOscHandlerFallback((...params: any[]) => { + this._terminal.error('Unknown OSC code: ', params); + }); // FIXME: remove temporary fix to get collect to terminal - this._parser.registerPrefixHandler((collect: string) => { this._terminal.prefix = collect; }); + this._parser.setPrefixHandler((collect: string) => { this._terminal.prefix = collect; }); // print handler - this._parser.registerPrintHandler(this.print.bind(this)); + this._parser.setPrintHandler(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', + this._parser.setCsiHandler('@', this.insertChars.bind(this)); + this._parser.setCsiHandler('A', this.cursorUp.bind(this)); + this._parser.setCsiHandler('B', this.cursorDown.bind(this)); + this._parser.setCsiHandler('C', this.cursorForward.bind(this)); + this._parser.setCsiHandler('D', this.cursorBackward.bind(this)); + this._parser.setCsiHandler('E', this.cursorNextLine.bind(this)); + this._parser.setCsiHandler('F', this.cursorPrecedingLine.bind(this)); + this._parser.setCsiHandler('G', this.cursorCharAbsolute.bind(this)); + this._parser.setCsiHandler('H', this.cursorPosition.bind(this)); + this._parser.setCsiHandler('I', this.cursorForwardTab.bind(this)); + this._parser.setCsiHandler('J', this.eraseInDisplay.bind(this)); + this._parser.setCsiHandler('K', this.eraseInLine.bind(this)); + this._parser.setCsiHandler('L', this.insertLines.bind(this)); + this._parser.setCsiHandler('M', this.deleteLines.bind(this)); + this._parser.setCsiHandler('P', this.deleteChars.bind(this)); + this._parser.setCsiHandler('S', this.scrollUp.bind(this)); + this._parser.setCsiHandler('T', (params, collect) => { if (params.length < 2 && !collect) { - return this._inputHandler.scrollDown(params); + return this.scrollDown(params); } }); - 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', + this._parser.setCsiHandler('X', this.eraseChars.bind(this)); + this._parser.setCsiHandler('Z', this.cursorBackwardTab.bind(this)); + this._parser.setCsiHandler('`', this.charPosAbsolute.bind(this)); + this._parser.setCsiHandler('a', this.HPositionRelative.bind(this)); + this._parser.setCsiHandler('b', this.repeatPrecedingCharacter.bind(this)); + this._parser.setCsiHandler('c', this.sendDeviceAttributes.bind(this)); // fix collect + this._parser.setCsiHandler('d', this.linePosAbsolute.bind(this)); + this._parser.setCsiHandler('e', this.VPositionRelative.bind(this)); + this._parser.setCsiHandler('f', this.HVPosition.bind(this)); + this._parser.setCsiHandler('g', this.tabClear.bind(this)); + this._parser.setCsiHandler('h', this.setMode.bind(this)); // fix collect + this._parser.setCsiHandler('l', this.resetMode.bind(this)); // fix collect + this._parser.setCsiHandler('m', this.charAttributes.bind(this)); + this._parser.setCsiHandler('n', this.deviceStatus.bind(this)); // fix collect + this._parser.setCsiHandler('p', (params, collect) => { if (collect === '!') { - return this._inputHandler.softReset(params); + return this.softReset(params); } }); - this._parser.registerCsiHandler('q', + this._parser.setCsiHandler('q', (params, collect) => { if (collect === ' ') { - return this._inputHandler.setCursorStyle(params); + return this.setCursorStyle(params); } }); - 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)); + this._parser.setCsiHandler('r', this.setScrollRegion.bind(this)); // fix collect + this._parser.setCsiHandler('s', this.saveCursor.bind(this)); + this._parser.setCsiHandler('u', this.restoreCursor.bind(this)); // 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)); + this._parser.setExecuteHandler(C0.BEL, this.bell.bind(this)); + this._parser.setExecuteHandler(C0.LF, this.lineFeed.bind(this)); + this._parser.setExecuteHandler(C0.VT, this.lineFeed.bind(this)); + this._parser.setExecuteHandler(C0.FF, this.lineFeed.bind(this)); + this._parser.setExecuteHandler(C0.CR, this.carriageReturn.bind(this)); + this._parser.setExecuteHandler(C0.BS, this.backspace.bind(this)); + this._parser.setExecuteHandler(C0.HT, this.tab.bind(this)); + this._parser.setExecuteHandler(C0.SO, this.shiftOut.bind(this)); + this._parser.setExecuteHandler(C0.SI, this.shiftIn.bind(this)); // 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)); + this._parser.setOscHandler(0, this._terminal.handleTitle.bind(this._terminal)); // 1 - icon name // 2 - title - this._parser.registerOscHandler(2, this._terminal.handleTitle.bind(this._terminal)); + this._parser.setOscHandler(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 @@ -715,62 +773,60 @@ export class ParserTerminal implements IParserTerminal { // 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._parser.setEscHandler('', '7', this.saveCursor.bind(this)); + this._parser.setEscHandler('', '8', this.restoreCursor.bind(this)); + this._parser.setEscHandler('', 'D', this._terminal.index.bind(this._terminal)); + this._parser.setEscHandler('', '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._parser.setEscHandler('', 'H', this._terminal.tabSet.bind(this._terminal)); + this._parser.setEscHandler('', 'M', this._terminal.reverseIndex.bind(this._terminal)); + this._parser.setEscHandler('', '=', () => { this._terminal.log('Serial port requested application keypad.'); this._terminal.applicationKeypad = true; if (this._terminal.viewport) { this._terminal.viewport.syncScrollArea(); } }); - this._parser.registerEscHandler('', '>', () => { + this._parser.setEscHandler('', '>', () => { 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._parser.setEscHandler('', 'c', this._terminal.reset.bind(this._terminal)); + this._parser.setEscHandler('', 'n', () => this._terminal.setgLevel(2)); + this._parser.setEscHandler('', 'o', () => this._terminal.setgLevel(3)); + this._parser.setEscHandler('', '|', () => this._terminal.setgLevel(3)); + this._parser.setEscHandler('', '}', () => this._terminal.setgLevel(2)); + this._parser.setEscHandler('', '~', () => this._terminal.setgLevel(1)); + + this._parser.setEscHandler('%', '@', () => { this._terminal.setgLevel(0); this._terminal.setgCharset(0, DEFAULT_CHARSET); // US (default) }); - this._parser.registerEscHandler('%', 'G', () => { + this._parser.setEscHandler('%', '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)); + this._parser.setEscHandler('(', flag, () => this._terminal.setgCharset(0, CHARSETS[flag] || DEFAULT_CHARSET)); + this._parser.setEscHandler(')', flag, () => this._terminal.setgCharset(1, CHARSETS[flag] || DEFAULT_CHARSET)); + this._parser.setEscHandler('*', flag, () => this._terminal.setgCharset(2, CHARSETS[flag] || DEFAULT_CHARSET)); + this._parser.setEscHandler('+', flag, () => this._terminal.setgCharset(3, CHARSETS[flag] || DEFAULT_CHARSET)); + this._parser.setEscHandler('-', flag, () => this._terminal.setgCharset(1, CHARSETS[flag] || DEFAULT_CHARSET)); + this._parser.setEscHandler('.', flag, () => this._terminal.setgCharset(2, CHARSETS[flag] || DEFAULT_CHARSET)); } // error handler - this._parser.registerErrorHandler((state) => { + this._parser.setErrorHandler((state) => { console.log('parsing error:', state); return state; }); } - - parse(data: string): void { const cursorStartX = this._terminal.buffer.x; const cursorStartY = this._terminal.buffer.y; @@ -816,7 +872,7 @@ export class ParserTerminal implements IParserTerminal { continue; } - // this._inputHandler.addChar(ch, code); + // this.addChar(ch, code); // calculate print space // expensive call, therefore we save width in line buffer diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 9e7ad3a51a..9aeeaa09d2 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -4,7 +4,7 @@ * @license MIT */ -import { CharData, IInputHandler, IInputHandlingTerminal } from './Types'; +import { CharData, IInputHandler } from './Types'; import { C0 } from './EscapeSequences'; import { DEFAULT_CHARSET } from './Charsets'; import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from './Buffer'; @@ -19,7 +19,7 @@ import { wcwidth } from './CharWidth'; * each function's header comment. */ export class InputHandler implements IInputHandler { - constructor(private _terminal: IInputHandlingTerminal) { } + constructor(protected _terminal: any) { } public addChar(char: string, code: number): void { // if (char >= ' ') { diff --git a/src/Terminal.ts b/src/Terminal.ts index 54548f9ef8..b6b509438d 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -334,7 +334,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this._inputHandler = new InputHandler(this); // this._parser = new Parser(this._inputHandler, this); - this._newParser = new ParserTerminal(this._inputHandler, this); + this._newParser = new ParserTerminal(this); // Reuse renderer if the Terminal is being recreated via a reset call. this.renderer = this.renderer || null; this.selectionManager = this.selectionManager || null; From dc14f5226d8517b5d3f98faba39f5ca2b45448c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 29 Apr 2018 19:34:43 +0200 Subject: [PATCH 12/44] fix InputHandler --- src/InputHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 9aeeaa09d2..9630530298 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -4,7 +4,7 @@ * @license MIT */ -import { CharData, IInputHandler } from './Types'; +import { CharData, IInputHandler, IInputHandlingTerminal } from './Types'; import { C0 } from './EscapeSequences'; import { DEFAULT_CHARSET } from './Charsets'; import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from './Buffer'; From 0f3d82a2a5190630bdb65fb4a0a09d8f025b215b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 29 Apr 2018 23:48:30 +0200 Subject: [PATCH 13/44] faster print action --- src/EscapeSequenceParser.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts index 4399428093..51573ab339 100644 --- a/src/EscapeSequenceParser.ts +++ b/src/EscapeSequenceParser.ts @@ -460,8 +460,11 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { code = data.charCodeAt(i); // shortcut for most chars (print action) - if (currentState === ParserState.GROUND && (code > 0x1f && code < 0x80)) { + 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; } From 826cca70889f9269a3b91f6ea98e2e4150f07e63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 30 Apr 2018 01:32:49 +0200 Subject: [PATCH 14/44] fix import issue --- src/InputHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 9630530298..9aeeaa09d2 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -4,7 +4,7 @@ * @license MIT */ -import { CharData, IInputHandler, IInputHandlingTerminal } from './Types'; +import { CharData, IInputHandler } from './Types'; import { C0 } from './EscapeSequences'; import { DEFAULT_CHARSET } from './Charsets'; import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from './Buffer'; From a55a7f369573b5c06310eb8da37ed62c0eff5891 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 30 Apr 2018 01:49:29 +0200 Subject: [PATCH 15/44] some cleanup --- src/EscapeSequenceParser.ts | 68 ++++++++++++++----------------------- 1 file changed, 25 insertions(+), 43 deletions(-) diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts index 51573ab339..12b93ad139 100644 --- a/src/EscapeSequenceParser.ts +++ b/src/EscapeSequenceParser.ts @@ -4,20 +4,6 @@ import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from './Buffer'; import { wcwidth } from './CharWidth'; import { InputHandler } from './InputHandler'; - -// terminal interface for the escape sequence parser -export interface IParserTerminal { - print?: (data: string, start: number, end: number) => void; - actionOSC?: (data: string) => void; - actionExecute?: (flag: string) => void; - actionCSI?: (collected: string, params: number[], flag: string) => void; - actionESC?: (collected: string, flag: string) => void; - actionDCSHook?: (collected: string, params: number[], flag: string) => void; - actionDCSPrint?: (data: string, start: number, end: number) => void; - actionDCSUnhook?: () => void; - actionError?: () => void; // FIXME: real signature and error handling -} - export interface IParsingState { position: number; // position in string code: number; // actual character code @@ -69,19 +55,19 @@ export interface IEscapeSequenceParser { setExecuteHandler(flag: string, callback: IExecuteHandler): void; clearExecuteHandler(flag: string): void; - setExecuteHandlerFallback(callback: (...params: any[]) => void); + setExecuteHandlerFallback(callback: (...params: any[]) => void): void; setCsiHandler(flag: string, callback: ICsiHandler): void; clearCsiHandler(flag: string): void; - setCsiHandlerFallback(callback: (...params: any[]) => 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); + setEscHandlerFallback(callback: (...params: any[]) => void): void; setOscHandler(ident: number, callback: IOscHandler): void; clearOscHandler(ident: number): void; - setOscHandlerFallback(callback: (...params: any[]) => void); + setOscHandlerFallback(callback: (...params: any[]) => void): void; setDcsHandler(collect: string, flag: string, handler: IDcsHandler): void; clearDcsHandler(collect: string, flag: string): void; @@ -375,7 +361,7 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { clearExecuteHandler(flag: string): void { if (this._executeHandlers[flag.charCodeAt(0)]) delete this._executeHandlers[flag.charCodeAt(0)]; } - setExecuteHandlerFallback(callback): void { + setExecuteHandlerFallback(callback: (...params: any[]) => void): void { this._escHandlerFb = callback; } @@ -385,7 +371,7 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { clearCsiHandler(flag: string): void { if (this._csiHandlers[flag.charCodeAt(0)]) delete this._csiHandlers[flag.charCodeAt(0)]; } - setCsiHandlerFallback(callback): void { + setCsiHandlerFallback(callback: (...params: any[]) => void): void { this._csiHandlerFb = callback; } @@ -395,7 +381,7 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { clearEscHandler(collect: string, flag: string): void { if (this._escHandlers[collect + flag]) delete this._escHandlers[collect + flag]; } - setEscHandlerFallback(callback): void { + setEscHandlerFallback(callback: (...params: any[]) => void): void { this._escHandlerFb = callback; } @@ -405,7 +391,7 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { clearOscHandler(ident: number): void { if (this._oscHandlers[ident]) delete this._oscHandlers[ident]; } - setOscHandlerFallback(callback): void { + setOscHandlerFallback(callback: (...params: any[]) => void): void { this._oscHandlerFb = callback; } @@ -503,20 +489,20 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { // keep the lookup table small if (code > 0x9f) { switch (currentState) { - case ParserState.GROUND: // add char to print string + case ParserState.GROUND: print = (~print) ? print : i; break; - case ParserState.OSC_STRING: // add char to osc string + case ParserState.OSC_STRING: osc += String.fromCharCode(code); transition |= ParserState.OSC_STRING; break; - case ParserState.CSI_IGNORE: // ignore char + case ParserState.CSI_IGNORE: transition |= ParserState.CSI_IGNORE; break; - case ParserState.DCS_IGNORE: // ignore char + case ParserState.DCS_IGNORE: transition |= ParserState.DCS_IGNORE; break; - case ParserState.DCS_PASSTHROUGH: // add char to dcs string + case ParserState.DCS_PASSTHROUGH: dcs = (~dcs) ? dcs : i; transition |= ParserState.DCS_PASSTHROUGH; break; @@ -530,15 +516,15 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { if (error) { let inject: IParsingState = this._errorHandler( { - position: i, // position in string - code, // actual character code - currentState, // current state - print, // print buffer start index - dcs, // dcs buffer start index - osc, // osc string buffer - collect, // collect buffer - params, // params buffer - abort: false // abort flag + position: i, + code, + currentState, + print, + dcs, + osc, + collect, + params, + abort: false }); if (inject.abort) return; // FIXME: inject return values @@ -651,15 +637,12 @@ export class ParserTerminal extends InputHandler { this._parser.setCsiHandlerFallback((...params: any[]) => { this._terminal.error('Unknown CSI code: ', params); }); - this._parser.setEscHandlerFallback((...params: any[]) => { this._terminal.error('Unknown ESC code: ', params); }); - this._parser.setExecuteHandlerFallback((...params: any[]) => { this._terminal.error('Unknown EXECUTE code: ', params); }); - this._parser.setOscHandlerFallback((...params: any[]) => { this._terminal.error('Unknown OSC code: ', params); }); @@ -839,7 +822,7 @@ export class ParserTerminal extends InputHandler { // apply leftover surrogate high from last write if (this._terminal.surrogate_high) { - data = this._terminal.surrogate_high + data; + data = this._terminal.surrogate_high + data; // FIXME: avoid string copy --> move to print this._terminal.surrogate_high = ''; } @@ -854,6 +837,7 @@ export class ParserTerminal extends InputHandler { let ch; let code; let low; + let chWidth; const buffer = this._terminal.buffer; for (let i = start; i < end; ++i) { ch = data.charAt(i); @@ -875,11 +859,9 @@ export class ParserTerminal extends InputHandler { continue; } - // this.addChar(ch, code); - // calculate print space // expensive call, therefore we save width in line buffer - const chWidth = wcwidth(code); + chWidth = wcwidth(code); if (this._terminal.charset && this._terminal.charset[ch]) { ch = this._terminal.charset[ch]; From f5375c3a99881fee6ac62c36bb06ba7f3135d143 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 30 Apr 2018 02:06:57 +0200 Subject: [PATCH 16/44] fix error in esc handler call --- src/EscapeSequenceParser.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts index 12b93ad139..4d736290cc 100644 --- a/src/EscapeSequenceParser.ts +++ b/src/EscapeSequenceParser.ts @@ -29,7 +29,7 @@ export interface ICsiHandler { } export interface IEscHandler { - (flag: string): void; + (collect: string, flag: number): void; } export interface IOscHandler { @@ -545,7 +545,7 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { break; case ParserAction.ESC_DISPATCH: ident = collect + String.fromCharCode(code); - if (this._escHandlers[ident]) this._escHandlers[ident](params, collect); + if (this._escHandlers[ident]) this._escHandlers[ident](collect, code); else this._escHandlerFb(collect, code); break; case ParserAction.CLEAR: From a1035269d50caf14a854c21d7ebee677864ca940 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 30 Apr 2018 11:27:10 +0200 Subject: [PATCH 17/44] add array fallback for TransitionTable --- src/EscapeSequenceParser.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts index 4d736290cc..d167a88fa8 100644 --- a/src/EscapeSequenceParser.ts +++ b/src/EscapeSequenceParser.ts @@ -130,13 +130,13 @@ function r(a: number, b: number): number[] { } -// transition table of the FSM -// TODO: fallback to array export class TransitionTable { - public table: Uint8Array; + public table: Uint8Array | number[]; constructor(length: number) { - this.table = new Uint8Array(length); + this.table = (typeof Uint32Array === 'undefined') + ? new Array(length) + : new Uint32Array(length); } add(inp: number, state: number, action: number | null, next: number | null): void { @@ -157,8 +157,8 @@ let EXECUTABLES = r(0x00, 0x18); EXECUTABLES.push(0x19); EXECUTABLES.concat(r(0x1c, 0x20)); -// default transition of the FSM is [error, GROUND] -let DEFAULT_TRANSITION = ParserAction.ERROR << 4 | ParserState.GROUND; +// 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 @@ -168,12 +168,12 @@ export const VT500_TRANSITION_TABLE = (function (): TransitionTable { let states: number[] = r(ParserState.GROUND, ParserState.DCS_PASSTHROUGH + 1); let state: any; - // table with default transition [any] --> [error, GROUND] + // 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[state << 8 | code] = DEFAULT_TRANSITION; + table.add(code, state, ParserAction.ERROR, ParserState.GROUND); } } @@ -292,7 +292,7 @@ class DcsDummy implements IDcsHandler { export class EscapeSequenceParser implements IEscapeSequenceParser { public initialState: number; public currentState: number; - public transitions: TransitionTable; + readonly transitions: TransitionTable; // buffers over several parse calls protected _osc: string; @@ -436,7 +436,7 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { let osc = this._osc; let collect = this._collect; let params = this._params; - let table: Uint8Array = this.transitions.table; + let table: Uint8Array | number[] = this.transitions.table; let dcsHandler: IDcsHandler | null = this._activeDcsHandler; let ident: string = ''; From b15817686236bf98c6e266283d977593fb4dd5ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 30 Apr 2018 14:25:29 +0200 Subject: [PATCH 18/44] local .buffer in parse --- src/EscapeSequenceParser.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts index d167a88fa8..e4cc56260c 100644 --- a/src/EscapeSequenceParser.ts +++ b/src/EscapeSequenceParser.ts @@ -808,14 +808,15 @@ export class ParserTerminal extends InputHandler { // error handler this._parser.setErrorHandler((state) => { - console.log('parsing error:', state); + this._terminal.error('Parsing error: ', state); return state; }); } parse(data: string): void { - const cursorStartX = this._terminal.buffer.x; - const cursorStartY = this._terminal.buffer.y; + let buffer = this._terminal.buffer; + const cursorStartX = buffer.x; + const cursorStartY = buffer.y; if (this._terminal.debug) { this._terminal.log('data: ' + data); } @@ -828,7 +829,8 @@ export class ParserTerminal extends InputHandler { this._parser.parse(data); - if (this._terminal.buffer.x !== cursorStartX || this._terminal.buffer.y !== cursorStartY) { + buffer = this._terminal.buffer; + if (buffer.x !== cursorStartX || buffer.y !== cursorStartY) { this._terminal.emit('cursormove'); } } From 645cd2eb2cf4f9e8b1bdd3bb876286ceb1ffbe70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 30 Apr 2018 15:38:17 +0200 Subject: [PATCH 19/44] fix test cases --- ..._disabled => EscapeSequenceParser.test.ts} | 189 ++++++------------ src/EscapeSequenceParser.ts | 54 ++--- 2 files changed, 94 insertions(+), 149 deletions(-) rename src/{EscapeSequenceParser.test.ts_disabled => EscapeSequenceParser.test.ts} (87%) diff --git a/src/EscapeSequenceParser.test.ts_disabled b/src/EscapeSequenceParser.test.ts similarity index 87% rename from src/EscapeSequenceParser.test.ts_disabled rename to src/EscapeSequenceParser.test.ts index fe62629039..97555e37a9 100644 --- a/src/EscapeSequenceParser.test.ts_disabled +++ b/src/EscapeSequenceParser.test.ts @@ -1,4 +1,4 @@ -import { EscapeSequenceParser, IParserTerminal, ParserState } from './EscapeSequenceParser'; +import { EscapeSequenceParser, ParserState, IDcsHandler } from './EscapeSequenceParser'; import * as chai from 'chai'; function r(a: number, b: number): string[] { @@ -10,13 +10,7 @@ function r(a: number, b: number): string[] { return arr; } -interface ITestTerminal extends IParserTerminal { - calls: any[]; - clear: () => void; - compare: (value: any) => void; -} - -let testTerminal: ITestTerminal = { +let testTerminal: any = { calls: [], clear: function (): void { this.calls = []; @@ -33,14 +27,14 @@ let testTerminal: ITestTerminal = { 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]); + actionCSI: function (collect: string, params: number[], flag: string): void { + this.calls.push(['csi', collect, params, flag]); }, - actionESC: function (collected: string, flag: string): void { - this.calls.push(['esc', collected, flag]); + actionESC: function (collect: string, flag: string): void { + this.calls.push(['esc', collect, flag]); }, - actionDCSHook: function (collected: string, params: number[], flag: string): void { - this.calls.push(['dcs hook', collected, params, flag]); + actionDCSHook: function (collect: string, params: number[], flag: string): void { + this.calls.push(['dcs hook', collect, params, flag]); }, actionDCSPrint: function (data: string, start: number, end: number): void { this.calls.push(['dcs put', data.substring(start, end)]); @@ -50,6 +44,18 @@ let testTerminal: ITestTerminal = { } }; +class DcsTest implements IDcsHandler { + hook(collect: string, params: number[], flag: number): void { + testTerminal.actionDCSHook(collect, params, String.fromCharCode(flag)); + } + put(data: string, start: number, end: number): void { + testTerminal.actionDCSPrint(data, start, end); + } + unhook(): void { + testTerminal.actionDCSUnhook(); + } +} + let states: number[] = [ ParserState.GROUND, ParserState.ESCAPE, @@ -68,54 +74,46 @@ let states: number[] = [ ]; let state: any; -let parser = new EscapeSequenceParser(testTerminal); -parser.registerPrintHandler(testTerminal.print.bind(testTerminal)); +let parser = new EscapeSequenceParser(); +parser.setPrintHandler(testTerminal.print.bind(testTerminal)); +parser.setCsiHandlerFallback((...params: any[]) => { + testTerminal.actionCSI(params[0], params[1], String.fromCharCode(params[2])); +}); +parser.setEscHandlerFallback((...params: any[]) => { + testTerminal.actionESC(params[0], String.fromCharCode(params[1])); +}); +parser.setExecuteHandlerFallback((...params: any[]) => { + testTerminal.actionExecute(String.fromCharCode(params[0])); +}); +parser.setOscHandlerFallback((...params: any[]) => { + if (params[0] === -1) testTerminal.actionOSC(params[1]); // handle error condition silently + else testTerminal.actionOSC(params[0] + ';' + params[1]); +}); +parser.setDcsHandlerFallback(new DcsTest); +// FIXME: to be removed +parser.setPrefixHandler(()=>{}); -describe('EscapeSequenceParser', function(): void { +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.initialState).equal(ParserState.GROUND); + chai.expect(parser.currentState).equal(ParserState.GROUND); chai.expect(parser.osc).equal(''); chai.expect(parser.params).eql([0]); - chai.expect(parser.collected).equal(''); + chai.expect(parser.collect).equal(''); }); it('reset states', function (): void { parser.currentState = 124; parser.osc = '#'; parser.params = [123]; - parser.collected = '#'; + parser.collect = '#'; 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(''); + chai.expect(parser.collect).equal(''); }); }); @@ -182,12 +180,12 @@ describe('EscapeSequenceParser', function(): void { parser.currentState = state; parser.osc = '#'; parser.params = [23]; - parser.collected = '#'; + parser.collect = '#'; 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(''); + chai.expect(parser.collect).equal(''); parser.reset(); } }); @@ -239,7 +237,7 @@ describe('EscapeSequenceParser', function(): void { parser.currentState = ParserState.ESCAPE; parser.parse(collect[i]); chai.expect(parser.currentState).equal(ParserState.ESCAPE_INTERMEDIATE); - chai.expect(parser.collected).equal(collect[i]); + chai.expect(parser.collect).equal(collect[i]); parser.reset(); } }); @@ -275,7 +273,7 @@ describe('EscapeSequenceParser', function(): void { parser.currentState = ParserState.ESCAPE_INTERMEDIATE; parser.parse(collect[i]); chai.expect(parser.currentState).equal(ParserState.ESCAPE_INTERMEDIATE); - chai.expect(parser.collected).equal(collect[i]); + chai.expect(parser.collect).equal(collect[i]); parser.reset(); } }); @@ -298,24 +296,24 @@ describe('EscapeSequenceParser', function(): void { parser.currentState = ParserState.ESCAPE; parser.osc = '#'; parser.params = [123]; - parser.collected = '#'; + parser.collect = '#'; 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(''); + chai.expect(parser.collect).equal(''); parser.reset(); // C1 for (state in states) { parser.currentState = state; parser.osc = '#'; parser.params = [123]; - parser.collected = '#'; + parser.collect = '#'; 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(''); + chai.expect(parser.collect).equal(''); parser.reset(); } }); @@ -376,7 +374,7 @@ describe('EscapeSequenceParser', function(): void { parser.currentState = ParserState.CSI_ENTRY; parser.parse(collect[i]); chai.expect(parser.currentState).equal(ParserState.CSI_PARAM); - chai.expect(parser.collected).equal(collect[i]); + chai.expect(parser.collect).equal(collect[i]); parser.reset(); } }); @@ -441,7 +439,7 @@ describe('EscapeSequenceParser', function(): void { parser.currentState = ParserState.CSI_ENTRY; parser.parse(collect[i]); chai.expect(parser.currentState).equal(ParserState.CSI_INTERMEDIATE); - chai.expect(parser.collected).equal(collect[i]); + chai.expect(parser.collect).equal(collect[i]); parser.reset(); } }); @@ -452,7 +450,7 @@ describe('EscapeSequenceParser', function(): void { parser.currentState = ParserState.CSI_PARAM; parser.parse(collect[i]); chai.expect(parser.currentState).equal(ParserState.CSI_INTERMEDIATE); - chai.expect(parser.collected).equal(collect[i]); + chai.expect(parser.collect).equal(collect[i]); parser.reset(); } }); @@ -478,7 +476,7 @@ describe('EscapeSequenceParser', function(): void { parser.currentState = ParserState.CSI_INTERMEDIATE; parser.parse(collect[i]); chai.expect(parser.currentState).equal(ParserState.CSI_INTERMEDIATE); - chai.expect(parser.collected).equal(collect[i]); + chai.expect(parser.collect).equal(collect[i]); parser.reset(); } }); @@ -695,7 +693,7 @@ describe('EscapeSequenceParser', function(): void { parser.currentState = ParserState.DCS_ENTRY; parser.parse(collect[i]); chai.expect(parser.currentState).equal(ParserState.DCS_PARAM); - chai.expect(parser.collected).equal(collect[i]); + chai.expect(parser.collect).equal(collect[i]); parser.reset(); } }); @@ -777,7 +775,7 @@ describe('EscapeSequenceParser', function(): void { parser.currentState = ParserState.DCS_ENTRY; parser.parse(collect[i]); chai.expect(parser.currentState).equal(ParserState.DCS_INTERMEDIATE); - chai.expect(parser.collected).equal(collect[i]); + chai.expect(parser.collect).equal(collect[i]); parser.reset(); } }); @@ -788,7 +786,7 @@ describe('EscapeSequenceParser', function(): void { parser.currentState = ParserState.DCS_PARAM; parser.parse(collect[i]); chai.expect(parser.currentState).equal(ParserState.DCS_INTERMEDIATE); - chai.expect(parser.collected).equal(collect[i]); + chai.expect(parser.collect).equal(collect[i]); parser.reset(); } }); @@ -812,7 +810,7 @@ describe('EscapeSequenceParser', function(): void { parser.currentState = ParserState.DCS_INTERMEDIATE; parser.parse(collect[i]); chai.expect(parser.currentState).equal(ParserState.DCS_INTERMEDIATE); - chai.expect(parser.collected).equal(collect[i]); + chai.expect(parser.collect).equal(collect[i]); parser.reset(); } }); @@ -823,7 +821,7 @@ describe('EscapeSequenceParser', function(): void { parser.currentState = ParserState.DCS_INTERMEDIATE; parser.parse('\x20' + chars[i]); chai.expect(parser.currentState).equal(ParserState.DCS_IGNORE); - chai.expect(parser.collected).equal('\x20'); + chai.expect(parser.collect).equal('\x20'); parser.reset(); } }); @@ -990,9 +988,9 @@ describe('EscapeSequenceParser', function(): void { parser.reset(); testTerminal.clear(); parser.currentState = ParserState.DCS_PASSTHROUGH; - parser.parse('€öäü'); + parser.parse('\x901;2;3+$a€öäü'); chai.expect(parser.currentState).equal(ParserState.DCS_PASSTHROUGH); - testTerminal.compare([['dcs put', '€öäü']]); + testTerminal.compare([['dcs hook', '+$', [1, 2, 3], 'a'], ['dcs put', '€öäü']]); parser.reset(); testTerminal.clear(); }); @@ -1007,64 +1005,5 @@ describe('EscapeSequenceParser', function(): void { 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(); - }); - }); - */ - + // TODO: error conditions, higher order: set/clear of callbacks, custom sequences }); diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts index e4cc56260c..4dd5953d16 100644 --- a/src/EscapeSequenceParser.ts +++ b/src/EscapeSequenceParser.ts @@ -295,9 +295,9 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { readonly transitions: TransitionTable; // buffers over several parse calls - protected _osc: string; - protected _params: number[]; - protected _collect: string; + public osc: string; + public params: number[]; + public collect: string; // callback slots protected _printHandler: IPrintHandler; @@ -325,9 +325,9 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { this.initialState = ParserState.GROUND; this.currentState = this.initialState; this.transitions = transitions; - this._osc = ''; - this._params = [0]; - this._collect = ''; + this.osc = ''; + this.params = [0]; + this.collect = ''; // set default fallback handlers this._printHandlerFb = (data, start, end): void => {}; @@ -362,7 +362,7 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { if (this._executeHandlers[flag.charCodeAt(0)]) delete this._executeHandlers[flag.charCodeAt(0)]; } setExecuteHandlerFallback(callback: (...params: any[]) => void): void { - this._escHandlerFb = callback; + this._executeHandlerFb = callback; } setCsiHandler(flag: string, callback: ICsiHandler): void { @@ -419,9 +419,9 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { reset(): void { this.currentState = this.initialState; - this._osc = ''; - this._params = [0]; - this._collect = ''; + this.osc = ''; + this.params = [0]; + this.collect = ''; } parse(data: string): void { @@ -433,9 +433,9 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { // local buffers let print = -1; let dcs = -1; - let osc = this._osc; - let collect = this._collect; - let params = this._params; + let osc = this.osc; + let collect = this.collect; + let params = this.params; let table: Uint8Array | number[] = this.transitions.table; let dcsHandler: IDcsHandler | null = this._activeDcsHandler; let ident: string = ''; @@ -568,9 +568,11 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { dcs = (~dcs) ? dcs : i; break; case ParserAction.DCS_UNHOOK: - if (~dcs) dcsHandler.put(data, dcs, i); - dcsHandler.unhook(); - dcsHandler = null; + if (dcsHandler) { + if (~dcs) dcsHandler.put(data, dcs, i); + dcsHandler.unhook(); + dcsHandler = null; + } if (code === 0x1b) transition |= ParserState.ESCAPE; osc = ''; params = [0]; @@ -590,10 +592,14 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { case ParserAction.OSC_END: 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 this._oscHandlerFb(identifier, content); + 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 = ''; @@ -608,14 +614,14 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { // push leftover pushable buffers to terminal if (currentState === ParserState.GROUND && ~print) { this._printHandler(data, print, data.length); - } else if (currentState === ParserState.DCS_PASSTHROUGH && ~dcs) { + } 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; + this.osc = osc; + this.collect = collect; + this.params = params; // save active dcs handler reference this._activeDcsHandler = dcsHandler; From dbc6b12ec2c4f723a164c9dfaf83ba98fe86d4ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 30 Apr 2018 15:44:21 +0200 Subject: [PATCH 20/44] missing whitespace --- src/EscapeSequenceParser.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EscapeSequenceParser.test.ts b/src/EscapeSequenceParser.test.ts index 97555e37a9..ff32c5c397 100644 --- a/src/EscapeSequenceParser.test.ts +++ b/src/EscapeSequenceParser.test.ts @@ -91,7 +91,7 @@ parser.setOscHandlerFallback((...params: any[]) => { }); parser.setDcsHandlerFallback(new DcsTest); // FIXME: to be removed -parser.setPrefixHandler(()=>{}); +parser.setPrefixHandler(() => {}); describe('EscapeSequenceParser', function(): void { From ba6fcddc433c3135d960d03f62440480cb61ae36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 30 Apr 2018 17:04:29 +0200 Subject: [PATCH 21/44] real merge of InputHandler and ParserTerminal --- src/EscapeSequenceParser.ts | 345 ++---------------------------------- src/InputHandler.ts | 264 +++++++++++++++++++++++++-- src/Parser.ts | 2 +- src/Terminal.ts | 9 +- src/Types.ts | 5 +- 5 files changed, 261 insertions(+), 364 deletions(-) diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts index 4dd5953d16..7de3857bfe 100644 --- a/src/EscapeSequenceParser.ts +++ b/src/EscapeSequenceParser.ts @@ -1,8 +1,9 @@ -import { CHARSETS, DEFAULT_CHARSET } from './Charsets'; -import { C0 } from './EscapeSequences'; -import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from './Buffer'; -import { wcwidth } from './CharWidth'; -import { InputHandler } from './InputHandler'; +/** + * TODO: + * - move interfaces to Types + * - docs + * - test cases + */ export interface IParsingState { position: number; // position in string @@ -157,7 +158,7 @@ let EXECUTABLES = r(0x00, 0x18); EXECUTABLES.push(0x19); EXECUTABLES.concat(r(0x1c, 0x20)); -// default transition is ParserAction.ERROR, ParserState.GROUND +// default transition is [ParserAction.ERROR, ParserState.GROUND] const DEFAULT_TRANSITION = ParserAction.ERROR << 4 | ParserState.GROUND; // default DEC/ANSI compatible state transition table @@ -287,8 +288,6 @@ class DcsDummy implements IDcsHandler { unhook(): void {} } -// default transition table points to global object -// Q: Copy table to allow custom sequences w'o changing global object? export class EscapeSequenceParser implements IEscapeSequenceParser { public initialState: number; public currentState: number; @@ -436,12 +435,12 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { let osc = this.osc; let collect = this.collect; let params = this.params; - let table: Uint8Array | number[] = this.transitions.table; + const table: Uint8Array | number[] = this.transitions.table; let dcsHandler: IDcsHandler | null = this._activeDcsHandler; - let ident: string = ''; + let ident: string = ''; // ugly workaround for ESC and DCS lookup keys // process input string - let l = data.length; + const l = data.length; for (let i = 0; i < l; ++i) { code = data.charCodeAt(i); @@ -630,327 +629,3 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { this.currentState = currentState; } } - - -export class ParserTerminal extends InputHandler { - private _parser: EscapeSequenceParser; - - constructor(_terminal: any) { - super(_terminal); - this._parser = new EscapeSequenceParser; - - // custom fallback handlers - this._parser.setCsiHandlerFallback((...params: any[]) => { - this._terminal.error('Unknown CSI code: ', params); - }); - this._parser.setEscHandlerFallback((...params: any[]) => { - this._terminal.error('Unknown ESC code: ', params); - }); - this._parser.setExecuteHandlerFallback((...params: any[]) => { - this._terminal.error('Unknown EXECUTE code: ', params); - }); - this._parser.setOscHandlerFallback((...params: any[]) => { - this._terminal.error('Unknown OSC code: ', params); - }); - - // FIXME: remove temporary fix to get collect to terminal - this._parser.setPrefixHandler((collect: string) => { this._terminal.prefix = collect; }); - - // print handler - this._parser.setPrintHandler(this.print.bind(this)); - - // CSI handler - this._parser.setCsiHandler('@', this.insertChars.bind(this)); - this._parser.setCsiHandler('A', this.cursorUp.bind(this)); - this._parser.setCsiHandler('B', this.cursorDown.bind(this)); - this._parser.setCsiHandler('C', this.cursorForward.bind(this)); - this._parser.setCsiHandler('D', this.cursorBackward.bind(this)); - this._parser.setCsiHandler('E', this.cursorNextLine.bind(this)); - this._parser.setCsiHandler('F', this.cursorPrecedingLine.bind(this)); - this._parser.setCsiHandler('G', this.cursorCharAbsolute.bind(this)); - this._parser.setCsiHandler('H', this.cursorPosition.bind(this)); - this._parser.setCsiHandler('I', this.cursorForwardTab.bind(this)); - this._parser.setCsiHandler('J', this.eraseInDisplay.bind(this)); - this._parser.setCsiHandler('K', this.eraseInLine.bind(this)); - this._parser.setCsiHandler('L', this.insertLines.bind(this)); - this._parser.setCsiHandler('M', this.deleteLines.bind(this)); - this._parser.setCsiHandler('P', this.deleteChars.bind(this)); - this._parser.setCsiHandler('S', this.scrollUp.bind(this)); - this._parser.setCsiHandler('T', - (params, collect) => { - if (params.length < 2 && !collect) { - return this.scrollDown(params); - } - }); - this._parser.setCsiHandler('X', this.eraseChars.bind(this)); - this._parser.setCsiHandler('Z', this.cursorBackwardTab.bind(this)); - this._parser.setCsiHandler('`', this.charPosAbsolute.bind(this)); - this._parser.setCsiHandler('a', this.HPositionRelative.bind(this)); - this._parser.setCsiHandler('b', this.repeatPrecedingCharacter.bind(this)); - this._parser.setCsiHandler('c', this.sendDeviceAttributes.bind(this)); // fix collect - this._parser.setCsiHandler('d', this.linePosAbsolute.bind(this)); - this._parser.setCsiHandler('e', this.VPositionRelative.bind(this)); - this._parser.setCsiHandler('f', this.HVPosition.bind(this)); - this._parser.setCsiHandler('g', this.tabClear.bind(this)); - this._parser.setCsiHandler('h', this.setMode.bind(this)); // fix collect - this._parser.setCsiHandler('l', this.resetMode.bind(this)); // fix collect - this._parser.setCsiHandler('m', this.charAttributes.bind(this)); - this._parser.setCsiHandler('n', this.deviceStatus.bind(this)); // fix collect - this._parser.setCsiHandler('p', - (params, collect) => { - if (collect === '!') { - return this.softReset(params); - } - }); - this._parser.setCsiHandler('q', - (params, collect) => { - if (collect === ' ') { - return this.setCursorStyle(params); - } - }); - this._parser.setCsiHandler('r', this.setScrollRegion.bind(this)); // fix collect - this._parser.setCsiHandler('s', this.saveCursor.bind(this)); - this._parser.setCsiHandler('u', this.restoreCursor.bind(this)); - - // execute handler - this._parser.setExecuteHandler(C0.BEL, this.bell.bind(this)); - this._parser.setExecuteHandler(C0.LF, this.lineFeed.bind(this)); - this._parser.setExecuteHandler(C0.VT, this.lineFeed.bind(this)); - this._parser.setExecuteHandler(C0.FF, this.lineFeed.bind(this)); - this._parser.setExecuteHandler(C0.CR, this.carriageReturn.bind(this)); - this._parser.setExecuteHandler(C0.BS, this.backspace.bind(this)); - this._parser.setExecuteHandler(C0.HT, this.tab.bind(this)); - this._parser.setExecuteHandler(C0.SO, this.shiftOut.bind(this)); - this._parser.setExecuteHandler(C0.SI, this.shiftIn.bind(this)); - // 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.setOscHandler(0, this._terminal.handleTitle.bind(this._terminal)); - // 1 - icon name - // 2 - title - this._parser.setOscHandler(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.setEscHandler('', '7', this.saveCursor.bind(this)); - this._parser.setEscHandler('', '8', this.restoreCursor.bind(this)); - this._parser.setEscHandler('', 'D', this._terminal.index.bind(this._terminal)); - this._parser.setEscHandler('', 'E', () => { - this._terminal.buffer.x = 0; - this._terminal.index(); - }); - this._parser.setEscHandler('', 'H', this._terminal.tabSet.bind(this._terminal)); - this._parser.setEscHandler('', 'M', this._terminal.reverseIndex.bind(this._terminal)); - this._parser.setEscHandler('', '=', () => { - this._terminal.log('Serial port requested application keypad.'); - this._terminal.applicationKeypad = true; - if (this._terminal.viewport) { - this._terminal.viewport.syncScrollArea(); - } - }); - this._parser.setEscHandler('', '>', () => { - this._terminal.log('Switching back to normal keypad.'); - this._terminal.applicationKeypad = false; - if (this._terminal.viewport) { - this._terminal.viewport.syncScrollArea(); - } - }); - this._parser.setEscHandler('', 'c', this._terminal.reset.bind(this._terminal)); - this._parser.setEscHandler('', 'n', () => this._terminal.setgLevel(2)); - this._parser.setEscHandler('', 'o', () => this._terminal.setgLevel(3)); - this._parser.setEscHandler('', '|', () => this._terminal.setgLevel(3)); - this._parser.setEscHandler('', '}', () => this._terminal.setgLevel(2)); - this._parser.setEscHandler('', '~', () => this._terminal.setgLevel(1)); - - this._parser.setEscHandler('%', '@', () => { - this._terminal.setgLevel(0); - this._terminal.setgCharset(0, DEFAULT_CHARSET); // US (default) - }); - this._parser.setEscHandler('%', 'G', () => { - this._terminal.setgLevel(0); - this._terminal.setgCharset(0, DEFAULT_CHARSET); // US (default) - }); - for (let flag in CHARSETS) { - this._parser.setEscHandler('(', flag, () => this._terminal.setgCharset(0, CHARSETS[flag] || DEFAULT_CHARSET)); - this._parser.setEscHandler(')', flag, () => this._terminal.setgCharset(1, CHARSETS[flag] || DEFAULT_CHARSET)); - this._parser.setEscHandler('*', flag, () => this._terminal.setgCharset(2, CHARSETS[flag] || DEFAULT_CHARSET)); - this._parser.setEscHandler('+', flag, () => this._terminal.setgCharset(3, CHARSETS[flag] || DEFAULT_CHARSET)); - this._parser.setEscHandler('-', flag, () => this._terminal.setgCharset(1, CHARSETS[flag] || DEFAULT_CHARSET)); - this._parser.setEscHandler('.', flag, () => this._terminal.setgCharset(2, CHARSETS[flag] || DEFAULT_CHARSET)); - } - - // error handler - this._parser.setErrorHandler((state) => { - this._terminal.error('Parsing error: ', state); - return state; - }); - } - - parse(data: string): void { - let buffer = this._terminal.buffer; - const cursorStartX = buffer.x; - const cursorStartY = buffer.y; - if (this._terminal.debug) { - this._terminal.log('data: ' + data); - } - - // apply leftover surrogate high from last write - if (this._terminal.surrogate_high) { - data = this._terminal.surrogate_high + data; // FIXME: avoid string copy --> move to print - this._terminal.surrogate_high = ''; - } - - this._parser.parse(data); - - buffer = this._terminal.buffer; - if (buffer.x !== cursorStartX || buffer.y !== cursorStartY) { - this._terminal.emit('cursormove'); - } - } - - print(data: string, start: number, end: number): void { - let ch; - let code; - let low; - let chWidth; - const buffer = this._terminal.buffer; - for (let i = start; i < end; ++i) { - ch = data.charAt(i); - code = data.charCodeAt(i); - if (0xD800 <= code && code <= 0xDBFF) { - // we got a surrogate high - // get surrogate low (next 2 bytes) - low = data.charCodeAt(i + 1); - if (isNaN(low)) { - // end of data stream, save surrogate high - this._terminal.surrogate_high = ch; - continue; - } - code = ((code - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000; - ch += data.charAt(i + 1); - } - // surrogate low - already handled above - if (0xDC00 <= code && code <= 0xDFFF) { - continue; - } - - // calculate print space - // expensive call, therefore we save width in line buffer - chWidth = wcwidth(code); - - if (this._terminal.charset && this._terminal.charset[ch]) { - ch = this._terminal.charset[ch]; - } - - if (this._terminal.options.screenReaderMode) { - this._terminal.emit('a11y.char', ch); - } - - let row = buffer.y + buffer.ybase; - - // insert combining char in last cell - // FIXME: needs handling after cursor jumps - if (!chWidth && buffer.x) { - // dont overflow left - if (buffer.lines.get(row)[buffer.x - 1]) { - if (!buffer.lines.get(row)[buffer.x - 1][CHAR_DATA_WIDTH_INDEX]) { - // found empty cell after fullwidth, need to go 2 cells back - if (buffer.lines.get(row)[buffer.x - 2]) { - buffer.lines.get(row)[buffer.x - 2][CHAR_DATA_CHAR_INDEX] += ch; - buffer.lines.get(row)[buffer.x - 2][3] = ch.charCodeAt(0); - } - } else { - buffer.lines.get(row)[buffer.x - 1][CHAR_DATA_CHAR_INDEX] += ch; - buffer.lines.get(row)[buffer.x - 1][3] = ch.charCodeAt(0); - } - this._terminal.updateRange(buffer.y); - } - continue; - } - - // goto next line if ch would overflow - // TODO: needs a global min terminal width of 2 - if (buffer.x + chWidth - 1 >= this._terminal.cols) { - // autowrap - DECAWM - if (this._terminal.wraparoundMode) { - buffer.x = 0; - buffer.y++; - if (buffer.y > buffer.scrollBottom) { - buffer.y--; - this._terminal.scroll(true); - } else { - // The line already exists (eg. the initial viewport), mark it as a - // wrapped line - (buffer.lines.get(buffer.y)).isWrapped = true; - } - } else { - if (chWidth === 2) { // FIXME: check for xterm behavior - continue; - } - } - } - row = buffer.y + buffer.ybase; - - // insert mode: move characters to right - if (this._terminal.insertMode) { - // do this twice for a fullwidth char - for (let moves = 0; moves < chWidth; ++moves) { - // remove last cell, if it's width is 0 - // we have to adjust the second last cell as well - const removed = buffer.lines.get(buffer.y + buffer.ybase).pop(); - if (removed[CHAR_DATA_WIDTH_INDEX] === 0 - && buffer.lines.get(row)[this._terminal.cols - 2] - && buffer.lines.get(row)[this._terminal.cols - 2][CHAR_DATA_WIDTH_INDEX] === 2) { - buffer.lines.get(row)[this._terminal.cols - 2] = [this._terminal.curAttr, ' ', 1, ' '.charCodeAt(0)]; - } - - // insert empty cell at cursor - buffer.lines.get(row).splice(buffer.x, 0, [this._terminal.curAttr, ' ', 1, ' '.charCodeAt(0)]); - } - } - - buffer.lines.get(row)[buffer.x] = [this._terminal.curAttr, ch, chWidth, ch.charCodeAt(0)]; - buffer.x++; - this._terminal.updateRange(buffer.y); - - // fullwidth char - set next cell width to zero and advance cursor - if (chWidth === 2) { - buffer.lines.get(row)[buffer.x] = [this._terminal.curAttr, '', 0, undefined]; - buffer.x++; - } - } - } -} diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 9aeeaa09d2..2561260d03 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -6,10 +6,11 @@ import { CharData, IInputHandler } from './Types'; import { C0 } from './EscapeSequences'; -import { DEFAULT_CHARSET } from './Charsets'; +import { CHARSETS, DEFAULT_CHARSET } from './Charsets'; import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from './Buffer'; import { FLAGS } from './renderer/Types'; import { wcwidth } from './CharWidth'; +import { EscapeSequenceParser } from './EscapeSequenceParser'; /** * The terminal's standard implementation of IInputHandler, this handles all @@ -19,24 +20,251 @@ import { wcwidth } from './CharWidth'; * each function's header comment. */ export class InputHandler implements IInputHandler { - constructor(protected _terminal: any) { } + private _parser: EscapeSequenceParser; + private _surrogateHigh: string; + + constructor(protected _terminal: any) { + this._parser = new EscapeSequenceParser; // FIXME: maybe as ctor argument + this._surrogateHigh = ''; + + // custom fallback handlers + this._parser.setCsiHandlerFallback((...params: any[]) => { + this._terminal.error('Unknown CSI code: ', params); + }); + this._parser.setEscHandlerFallback((...params: any[]) => { + this._terminal.error('Unknown ESC code: ', params); + }); + this._parser.setExecuteHandlerFallback((...params: any[]) => { + this._terminal.error('Unknown EXECUTE code: ', params); + }); + this._parser.setOscHandlerFallback((...params: any[]) => { + this._terminal.error('Unknown OSC code: ', params); + }); + + // FIXME: remove temporary fix to get collect to terminal + this._parser.setPrefixHandler((collect: string) => { this._terminal.prefix = collect; }); + + // print handler + this._parser.setPrintHandler(this.print.bind(this)); + + // CSI handler + this._parser.setCsiHandler('@', this.insertChars.bind(this)); + this._parser.setCsiHandler('A', this.cursorUp.bind(this)); + this._parser.setCsiHandler('B', this.cursorDown.bind(this)); + this._parser.setCsiHandler('C', this.cursorForward.bind(this)); + this._parser.setCsiHandler('D', this.cursorBackward.bind(this)); + this._parser.setCsiHandler('E', this.cursorNextLine.bind(this)); + this._parser.setCsiHandler('F', this.cursorPrecedingLine.bind(this)); + this._parser.setCsiHandler('G', this.cursorCharAbsolute.bind(this)); + this._parser.setCsiHandler('H', this.cursorPosition.bind(this)); + this._parser.setCsiHandler('I', this.cursorForwardTab.bind(this)); + this._parser.setCsiHandler('J', this.eraseInDisplay.bind(this)); + this._parser.setCsiHandler('K', this.eraseInLine.bind(this)); + this._parser.setCsiHandler('L', this.insertLines.bind(this)); + this._parser.setCsiHandler('M', this.deleteLines.bind(this)); + this._parser.setCsiHandler('P', this.deleteChars.bind(this)); + this._parser.setCsiHandler('S', this.scrollUp.bind(this)); + this._parser.setCsiHandler('T', + (params, collect) => { + if (params.length < 2 && !collect) { + return this.scrollDown(params); + } + }); + this._parser.setCsiHandler('X', this.eraseChars.bind(this)); + this._parser.setCsiHandler('Z', this.cursorBackwardTab.bind(this)); + this._parser.setCsiHandler('`', this.charPosAbsolute.bind(this)); + this._parser.setCsiHandler('a', this.HPositionRelative.bind(this)); + this._parser.setCsiHandler('b', this.repeatPrecedingCharacter.bind(this)); + this._parser.setCsiHandler('c', this.sendDeviceAttributes.bind(this)); // fix collect + this._parser.setCsiHandler('d', this.linePosAbsolute.bind(this)); + this._parser.setCsiHandler('e', this.VPositionRelative.bind(this)); + this._parser.setCsiHandler('f', this.HVPosition.bind(this)); + this._parser.setCsiHandler('g', this.tabClear.bind(this)); + this._parser.setCsiHandler('h', this.setMode.bind(this)); // fix collect + this._parser.setCsiHandler('l', this.resetMode.bind(this)); // fix collect + this._parser.setCsiHandler('m', this.charAttributes.bind(this)); + this._parser.setCsiHandler('n', this.deviceStatus.bind(this)); // fix collect + this._parser.setCsiHandler('p', + (params, collect) => { + if (collect === '!') { + return this.softReset(params); + } + }); + this._parser.setCsiHandler('q', + (params, collect) => { + if (collect === ' ') { + return this.setCursorStyle(params); + } + }); + this._parser.setCsiHandler('r', this.setScrollRegion.bind(this)); // fix collect + this._parser.setCsiHandler('s', this.saveCursor.bind(this)); + this._parser.setCsiHandler('u', this.restoreCursor.bind(this)); + + // execute handler + this._parser.setExecuteHandler(C0.BEL, this.bell.bind(this)); + this._parser.setExecuteHandler(C0.LF, this.lineFeed.bind(this)); + this._parser.setExecuteHandler(C0.VT, this.lineFeed.bind(this)); + this._parser.setExecuteHandler(C0.FF, this.lineFeed.bind(this)); + this._parser.setExecuteHandler(C0.CR, this.carriageReturn.bind(this)); + this._parser.setExecuteHandler(C0.BS, this.backspace.bind(this)); + this._parser.setExecuteHandler(C0.HT, this.tab.bind(this)); + this._parser.setExecuteHandler(C0.SO, this.shiftOut.bind(this)); + this._parser.setExecuteHandler(C0.SI, this.shiftIn.bind(this)); + // 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.setOscHandler(0, this._terminal.handleTitle.bind(this._terminal)); + // 1 - icon name + // 2 - title + this._parser.setOscHandler(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.setEscHandler('', '7', this.saveCursor.bind(this)); + this._parser.setEscHandler('', '8', this.restoreCursor.bind(this)); + this._parser.setEscHandler('', 'D', this._terminal.index.bind(this._terminal)); + this._parser.setEscHandler('', 'E', () => { + this._terminal.buffer.x = 0; + this._terminal.index(); + }); + this._parser.setEscHandler('', 'H', this._terminal.tabSet.bind(this._terminal)); + this._parser.setEscHandler('', 'M', this._terminal.reverseIndex.bind(this._terminal)); + this._parser.setEscHandler('', '=', () => { + this._terminal.log('Serial port requested application keypad.'); + this._terminal.applicationKeypad = true; + if (this._terminal.viewport) { + this._terminal.viewport.syncScrollArea(); + } + }); + this._parser.setEscHandler('', '>', () => { + this._terminal.log('Switching back to normal keypad.'); + this._terminal.applicationKeypad = false; + if (this._terminal.viewport) { + this._terminal.viewport.syncScrollArea(); + } + }); + this._parser.setEscHandler('', 'c', this._terminal.reset.bind(this._terminal)); + this._parser.setEscHandler('', 'n', () => this._terminal.setgLevel(2)); + this._parser.setEscHandler('', 'o', () => this._terminal.setgLevel(3)); + this._parser.setEscHandler('', '|', () => this._terminal.setgLevel(3)); + this._parser.setEscHandler('', '}', () => this._terminal.setgLevel(2)); + this._parser.setEscHandler('', '~', () => this._terminal.setgLevel(1)); + + this._parser.setEscHandler('%', '@', () => { + this._terminal.setgLevel(0); + this._terminal.setgCharset(0, DEFAULT_CHARSET); // US (default) + }); + this._parser.setEscHandler('%', 'G', () => { + this._terminal.setgLevel(0); + this._terminal.setgCharset(0, DEFAULT_CHARSET); // US (default) + }); + for (let flag in CHARSETS) { + this._parser.setEscHandler('(', flag, () => this._terminal.setgCharset(0, CHARSETS[flag] || DEFAULT_CHARSET)); + this._parser.setEscHandler(')', flag, () => this._terminal.setgCharset(1, CHARSETS[flag] || DEFAULT_CHARSET)); + this._parser.setEscHandler('*', flag, () => this._terminal.setgCharset(2, CHARSETS[flag] || DEFAULT_CHARSET)); + this._parser.setEscHandler('+', flag, () => this._terminal.setgCharset(3, CHARSETS[flag] || DEFAULT_CHARSET)); + this._parser.setEscHandler('-', flag, () => this._terminal.setgCharset(1, CHARSETS[flag] || DEFAULT_CHARSET)); + this._parser.setEscHandler('.', flag, () => this._terminal.setgCharset(2, CHARSETS[flag] || DEFAULT_CHARSET)); + } - public addChar(char: string, code: number): void { - // if (char >= ' ') { + // error handler + this._parser.setErrorHandler((state) => { + this._terminal.error('Parsing error: ', state); + return state; + }); + } - // make buffer local for faster access - const buffer = this._terminal.buffer; + public parse(data: string): void { + let buffer = this._terminal.buffer; + const cursorStartX = buffer.x; + const cursorStartY = buffer.y; + if (this._terminal.debug) { + this._terminal.log('data: ' + data); + } + + // apply leftover surrogate high from last write + if (this._surrogateHigh) { + data = this._surrogateHigh + data; + this._surrogateHigh = ''; + } + + this._parser.parse(data); + + buffer = this._terminal.buffer; + if (buffer.x !== cursorStartX || buffer.y !== cursorStartY) { + this._terminal.emit('cursormove'); + } + } + + public print(data: string, start: number, end: number): void { + let ch; + let code; + let low; + let chWidth; + const buffer = this._terminal.buffer; + for (let i = start; i < end; ++i) { + ch = data.charAt(i); + code = data.charCodeAt(i); + if (0xD800 <= code && code <= 0xDBFF) { + // we got a surrogate high + // get surrogate low (next 2 bytes) + low = data.charCodeAt(i + 1); + if (isNaN(low)) { + // end of data stream, save surrogate high + this._surrogateHigh = ch; + continue; + } + code = ((code - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000; + ch += data.charAt(i + 1); + } + // surrogate low - already handled above + if (0xDC00 <= code && code <= 0xDFFF) { + continue; + } // calculate print space // expensive call, therefore we save width in line buffer - const chWidth = wcwidth(code); + chWidth = wcwidth(code); - if (this._terminal.charset && this._terminal.charset[char]) { - char = this._terminal.charset[char]; + if (this._terminal.charset && this._terminal.charset[ch]) { + ch = this._terminal.charset[ch]; } if (this._terminal.options.screenReaderMode) { - this._terminal.emit('a11y.char', char); + this._terminal.emit('a11y.char', ch); } let row = buffer.y + buffer.ybase; @@ -49,16 +277,16 @@ export class InputHandler implements IInputHandler { if (!buffer.lines.get(row)[buffer.x - 1][CHAR_DATA_WIDTH_INDEX]) { // found empty cell after fullwidth, need to go 2 cells back if (buffer.lines.get(row)[buffer.x - 2]) { - buffer.lines.get(row)[buffer.x - 2][CHAR_DATA_CHAR_INDEX] += char; - buffer.lines.get(row)[buffer.x - 2][3] = char.charCodeAt(0); + buffer.lines.get(row)[buffer.x - 2][CHAR_DATA_CHAR_INDEX] += ch; + buffer.lines.get(row)[buffer.x - 2][3] = ch.charCodeAt(0); } } else { - buffer.lines.get(row)[buffer.x - 1][CHAR_DATA_CHAR_INDEX] += char; - buffer.lines.get(row)[buffer.x - 1][3] = char.charCodeAt(0); + buffer.lines.get(row)[buffer.x - 1][CHAR_DATA_CHAR_INDEX] += ch; + buffer.lines.get(row)[buffer.x - 1][3] = ch.charCodeAt(0); } this._terminal.updateRange(buffer.y); } - return; + continue; } // goto next line if ch would overflow @@ -78,7 +306,7 @@ export class InputHandler implements IInputHandler { } } else { if (chWidth === 2) { // FIXME: check for xterm behavior - return; + continue; } } } @@ -102,7 +330,7 @@ export class InputHandler implements IInputHandler { } } - buffer.lines.get(row)[buffer.x] = [this._terminal.curAttr, char, chWidth, char.charCodeAt(0)]; + buffer.lines.get(row)[buffer.x] = [this._terminal.curAttr, ch, chWidth, ch.charCodeAt(0)]; buffer.x++; this._terminal.updateRange(buffer.y); @@ -111,7 +339,7 @@ export class InputHandler implements IInputHandler { buffer.lines.get(row)[buffer.x] = [this._terminal.curAttr, '', 0, undefined]; buffer.x++; } - // } + } } /** diff --git a/src/Parser.ts b/src/Parser.ts index 372d844390..d3241307bf 100644 --- a/src/Parser.ts +++ b/src/Parser.ts @@ -232,7 +232,7 @@ export class Parser { if (ch in normalStateHandler) { normalStateHandler[ch](this, this._inputHandler); } else { - this._inputHandler.addChar(ch, code); + // this._inputHandler.addChar(ch, code); } break; case ParserState.ESCAPED: diff --git a/src/Terminal.ts b/src/Terminal.ts index b6b509438d..fa2ce01e50 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -49,7 +49,6 @@ import { AccessibilityManager } from './AccessibilityManager'; import { ScreenDprMonitor } from './utils/ScreenDprMonitor'; import { ITheme, ILocalizableStrings, IMarker, IDisposable } from 'xterm'; import { removeTerminalFromCache } from './renderer/atlas/CharAtlas'; -import { ParserTerminal } from './EscapeSequenceParser'; // reg + shift key mappings for digits and special chars const KEYCODE_KEY_MAPPINGS = { @@ -217,8 +216,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II private _inputHandler: InputHandler; public soundManager: SoundManager; - // private _parser: Parser; - private _newParser: ParserTerminal; public renderer: IRenderer; public selectionManager: SelectionManager; public linkifier: ILinkifier; @@ -333,8 +330,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this._userScrolling = false; this._inputHandler = new InputHandler(this); - // this._parser = new Parser(this._inputHandler, this); - this._newParser = new ParserTerminal(this); // Reuse renderer if the Terminal is being recreated via a reset call. this.renderer = this.renderer || null; this.selectionManager = this.selectionManager || null; @@ -1319,9 +1314,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // state of the parser resets to 0 after exiting parser.parse. This change // just sets the state back based on the correct return statement. - // const state = this._parser.parse(data); - this._newParser.parse(data); - // this._parser.setState(state); + this._inputHandler.parse(data); this.updateRange(this.buffer.y); this.refresh(this._refreshStart, this._refreshEnd); diff --git a/src/Types.ts b/src/Types.ts index 15498ce1f3..5e2ee90735 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -106,10 +106,11 @@ export interface ICompositionHelper { } /** - * Handles actions generated by the parser. + * Calls the parser and handles actions generated by the parser. */ export interface IInputHandler { - addChar(char: string, code: number): void; + parse(data: string): void; + print(data: string, start: number, end: number): void; /** C0 BEL */ bell(): void; /** C0 LF */ lineFeed(): void; From af392a6305bcf6e51a9cd56282f324134ad51cdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 30 Apr 2018 23:35:54 +0200 Subject: [PATCH 22/44] cleanup, some docs, temp fix for InputHandler tests --- src/EscapeSequenceParser.test.ts | 3 +- src/EscapeSequenceParser.ts | 1103 ++++++++++++++---------------- src/InputHandler.ts | 5 +- src/Terminal.ts | 1 + src/Types.ts | 172 ++++- 5 files changed, 686 insertions(+), 598 deletions(-) diff --git a/src/EscapeSequenceParser.test.ts b/src/EscapeSequenceParser.test.ts index ff32c5c397..e33e4b7e75 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 7de3857bfe..ea8e09ee2f 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[]; - - 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); - } - - 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); - } + public table: Uint8Array | number[]; + + constructor(length: number) { + this.table = (typeof Uint32Array === 'undefined') + ? new Array(length) + : new Uint32Array(length); + } + + /** + * 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); + } + + /** + * 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 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); - } - } + let table: TransitionTable = new TransitionTable(4095); - // 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 + let states: number[] = r(ParserState.GROUND, ParserState.DCS_PASSTHROUGH + 1); + let state: any; + + // 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); } - // 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; - - // buffers over several parse calls - 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; - - // 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; - - 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; - - 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; - } - - 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; - } - - 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; - } - - 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; + public initialState: number; + public currentState: number; + readonly transitions: TransitionTable; + + // 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; + + // 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; + + 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; + 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; + } + + 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; + } + + 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; + } + + 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; + } + + // 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 = ''; + } + + 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 + + // 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 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; + 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 (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); + } + } + if (code === 0x1b) transition |= ParserState.ESCAPE; + osc = ''; + params = [0]; + collect = ''; + dcs = -1; + break; + } + currentState = transition & 15; } - // FIXME: to be removed - setPrefixHandler(callback: (collect: string) => void): void { - this._tempPrefixHandler = callback; + // 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); } - reset(): void { - this.currentState = this.initialState; - this.osc = ''; - this.params = [0]; - this.collect = ''; - } + // save non pushable buffers + this.osc = osc; + this.collect = collect; + this.params = params; - 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 - - // 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 CSI params - if (currentState === ParserState.CSI_PARAM && (code > 0x2f && code < 0x39)) { - params[params.length - 1] = params[params.length - 1] * 10 + code - 48; - continue; - } + // save active dcs handler reference + this._activeDcsHandler = dcsHandler; - // 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; - } + // save state + this.currentState = currentState; + } } diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 2561260d03..8d023dab85 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 fa2ce01e50..078266d1f6 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 5e2ee90735..08e9f59268 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; +} From aeae688b4c211a3bc61ff64d28dcdde7b07953bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 1 May 2018 14:32:08 +0200 Subject: [PATCH 23/44] multiple changes in InputHandler: - remove prefix/postfix - optional collect arg where needed - tempPrefix handler removed - replaced .bind with () => notation - moved callback registering to ctor again --- src/EscapeSequenceParser.test.ts | 2 - src/EscapeSequenceParser.ts | 39 +++------ src/InputHandler.test.ts | 6 +- src/InputHandler.ts | 141 +++++++++++++++---------------- src/Terminal.ts | 5 -- src/Types.ts | 67 ++------------- src/utils/TestUtils.test.ts | 1 - 7 files changed, 90 insertions(+), 171 deletions(-) diff --git a/src/EscapeSequenceParser.test.ts b/src/EscapeSequenceParser.test.ts index e33e4b7e75..1c5b6d6c62 100644 --- a/src/EscapeSequenceParser.test.ts +++ b/src/EscapeSequenceParser.test.ts @@ -91,8 +91,6 @@ parser.setOscHandlerFallback((...params: any[]) => { else testTerminal.actionOSC(params[0] + ';' + params[1]); }); parser.setDcsHandlerFallback(new DcsTest); -// FIXME: to be removed -parser.setPrefixHandler(() => {}); describe('EscapeSequenceParser', function(): void { diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts index ea8e09ee2f..7b336d060a 100644 --- a/src/EscapeSequenceParser.ts +++ b/src/EscapeSequenceParser.ts @@ -3,11 +3,7 @@ * - docs * - extend test cases */ -import { - ParserState, ParserAction, IParsingState, IPrintHandler, - IExecuteHandler, ICsiHandler, IEscHandler, IOscHandler, - IDcsHandler, IErrorHandler, IEscapeSequenceParser -} from './Types'; +import { ParserState, ParserAction, IParsingState, IDcsHandler, IEscapeSequenceParser } from './Types'; // number range macro function r(a: number, b: number): number[] { @@ -213,26 +209,23 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { public collect: string; // callback slots - protected _printHandler: IPrintHandler; + protected _printHandler: (data: string, start: number, end: number) => void; protected _executeHandlers: any; protected _csiHandlers: any; protected _escHandlers: any; protected _oscHandlers: any; protected _dcsHandlers: any; protected _activeDcsHandler: IDcsHandler | null; - protected _errorHandler: IErrorHandler; + protected _errorHandler: (state: IParsingState) => IParsingState; // fallback handlers - protected _printHandlerFb: IPrintHandler; + protected _printHandlerFb: (data: string, start: number, end: number) => void; 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; + protected _errorHandlerFb: (state: IParsingState) => IParsingState; constructor(transitions: TransitionTable = VT500_TRANSITION_TABLE) { this.initialState = ParserState.GROUND; @@ -260,14 +253,14 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { this._errorHandler = this._errorHandlerFb; } - setPrintHandler(callback: IPrintHandler): void { + setPrintHandler(callback: (data: string, start: number, end: number) => void): void { this._printHandler = callback; } clearPrintHandler(): void { this._printHandler = this._printHandlerFb; } - setExecuteHandler(flag: string, callback: IExecuteHandler): void { + setExecuteHandler(flag: string, callback: () => void): void { this._executeHandlers[flag.charCodeAt(0)] = callback; } clearExecuteHandler(flag: string): void { @@ -277,7 +270,7 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { this._executeHandlerFb = callback; } - setCsiHandler(flag: string, callback: ICsiHandler): void { + setCsiHandler(flag: string, callback: (params: number[], collect: string) => void): void { this._csiHandlers[flag.charCodeAt(0)] = callback; } clearCsiHandler(flag: string): void { @@ -287,7 +280,7 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { this._csiHandlerFb = callback; } - setEscHandler(collect: string, flag: string, callback: IEscHandler): void { + setEscHandler(collect: string, flag: string, callback: (collect: string, flag: number) => void): void { this._escHandlers[collect + flag] = callback; } clearEscHandler(collect: string, flag: string): void { @@ -297,7 +290,7 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { this._escHandlerFb = callback; } - setOscHandler(ident: number, callback: IOscHandler): void { + setOscHandler(ident: number, callback: (data: string) => void): void { this._oscHandlers[ident] = callback; } clearOscHandler(ident: number): void { @@ -317,18 +310,13 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { this._dcsHandlerFb = handler; } - setErrorHandler(callback: IErrorHandler): void { + setErrorHandler(callback: (state: IParsingState) => IParsingState): void { this._errorHandler = callback; } clearErrorHandler(): void { this._errorHandler = this._errorHandlerFb; } - // FIXME: to be removed - setPrefixHandler(callback: (collect: string) => void): void { - this._tempPrefixHandler = callback; - } - reset(): void { this.currentState = this.initialState; this.osc = ''; @@ -397,8 +385,8 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { } break; case ParserAction.ERROR: - // chars higher than 0x9f are handled by this action to - // keep the lookup table small + // chars higher than 0x9f are handled by this action + // to keep the transition table small if (code > 0x9f) { switch (currentState) { case ParserState.GROUND: @@ -444,7 +432,6 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { } 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; diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index eb7b70da99..2ecec89a8d 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -68,14 +68,14 @@ describe('InputHandler', () => { describe('setMode', () => { it('should toggle Terminal.bracketedPasteMode', () => { let terminal = new MockInputHandlingTerminal(); - terminal.prefix = '?'; + const collect = '?'; terminal.bracketedPasteMode = false; let inputHandler = new InputHandler(terminal); // Set bracketed paste mode - inputHandler.setMode([2004]); + inputHandler.setMode([2004], collect); assert.equal(terminal.bracketedPasteMode, true); // Reset bracketed paste mode - inputHandler.resetMode([2004]); + inputHandler.resetMode([2004], collect); assert.equal(terminal.bracketedPasteMode, false); }); }); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 8d023dab85..8a614f1703 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -23,10 +23,7 @@ export class InputHandler implements IInputHandler { private _parser: EscapeSequenceParser; private _surrogateHigh: string; - constructor(protected _terminal: any) { } - - // FIXME: temp workaround to get tests working again - init(): void { + constructor(protected _terminal: any) { this._parser = new EscapeSequenceParser; // FIXME: maybe as ctor argument this._surrogateHigh = ''; @@ -44,49 +41,47 @@ export class InputHandler implements IInputHandler { this._terminal.error('Unknown OSC code: ', params); }); - // FIXME: remove temporary fix to get collect to terminal - this._parser.setPrefixHandler((collect: string) => { this._terminal.prefix = collect; }); - // print handler - this._parser.setPrintHandler(this.print.bind(this)); + this._parser.setPrintHandler( + (data: string, start: number, end: number): void => this.print(data, start, end)); // CSI handler - this._parser.setCsiHandler('@', this.insertChars.bind(this)); - this._parser.setCsiHandler('A', this.cursorUp.bind(this)); - this._parser.setCsiHandler('B', this.cursorDown.bind(this)); - this._parser.setCsiHandler('C', this.cursorForward.bind(this)); - this._parser.setCsiHandler('D', this.cursorBackward.bind(this)); - this._parser.setCsiHandler('E', this.cursorNextLine.bind(this)); - this._parser.setCsiHandler('F', this.cursorPrecedingLine.bind(this)); - this._parser.setCsiHandler('G', this.cursorCharAbsolute.bind(this)); - this._parser.setCsiHandler('H', this.cursorPosition.bind(this)); - this._parser.setCsiHandler('I', this.cursorForwardTab.bind(this)); - this._parser.setCsiHandler('J', this.eraseInDisplay.bind(this)); - this._parser.setCsiHandler('K', this.eraseInLine.bind(this)); - this._parser.setCsiHandler('L', this.insertLines.bind(this)); - this._parser.setCsiHandler('M', this.deleteLines.bind(this)); - this._parser.setCsiHandler('P', this.deleteChars.bind(this)); - this._parser.setCsiHandler('S', this.scrollUp.bind(this)); + this._parser.setCsiHandler('@', (params, collect) => this.insertChars(params)); + this._parser.setCsiHandler('A', (params, collect) => this.cursorUp(params)); + this._parser.setCsiHandler('B', (params, collect) => this.cursorDown(params)); + this._parser.setCsiHandler('C', (params, collect) => this.cursorForward(params)); + this._parser.setCsiHandler('D', (params, collect) => this.cursorBackward(params)); + this._parser.setCsiHandler('E', (params, collect) => this.cursorNextLine(params)); + this._parser.setCsiHandler('F', (params, collect) => this.cursorPrecedingLine(params)); + this._parser.setCsiHandler('G', (params, collect) => this.cursorCharAbsolute(params)); + this._parser.setCsiHandler('H', (params, collect) => this.cursorPosition(params)); + this._parser.setCsiHandler('I', (params, collect) => this.cursorForwardTab(params)); + this._parser.setCsiHandler('J', (params, collect) => this.eraseInDisplay(params)); + this._parser.setCsiHandler('K', (params, collect) => this.eraseInLine(params)); + this._parser.setCsiHandler('L', (params, collect) => this.insertLines(params)); + this._parser.setCsiHandler('M', (params, collect) => this.deleteLines(params)); + this._parser.setCsiHandler('P', (params, collect) => this.deleteChars(params)); + this._parser.setCsiHandler('S', (params, collect) => this.scrollUp(params)); this._parser.setCsiHandler('T', (params, collect) => { if (params.length < 2 && !collect) { return this.scrollDown(params); } }); - this._parser.setCsiHandler('X', this.eraseChars.bind(this)); - this._parser.setCsiHandler('Z', this.cursorBackwardTab.bind(this)); - this._parser.setCsiHandler('`', this.charPosAbsolute.bind(this)); - this._parser.setCsiHandler('a', this.HPositionRelative.bind(this)); - this._parser.setCsiHandler('b', this.repeatPrecedingCharacter.bind(this)); - this._parser.setCsiHandler('c', this.sendDeviceAttributes.bind(this)); // fix collect - this._parser.setCsiHandler('d', this.linePosAbsolute.bind(this)); - this._parser.setCsiHandler('e', this.VPositionRelative.bind(this)); - this._parser.setCsiHandler('f', this.HVPosition.bind(this)); - this._parser.setCsiHandler('g', this.tabClear.bind(this)); - this._parser.setCsiHandler('h', this.setMode.bind(this)); // fix collect - this._parser.setCsiHandler('l', this.resetMode.bind(this)); // fix collect - this._parser.setCsiHandler('m', this.charAttributes.bind(this)); - this._parser.setCsiHandler('n', this.deviceStatus.bind(this)); // fix collect + this._parser.setCsiHandler('X', (params, collect) => this.eraseChars(params)); + this._parser.setCsiHandler('Z', (params, collect) => this.cursorBackwardTab(params)); + this._parser.setCsiHandler('`', (params, collect) => this.charPosAbsolute(params)); + this._parser.setCsiHandler('a', (params, collect) => this.HPositionRelative(params)); + this._parser.setCsiHandler('b', (params, collect) => this.repeatPrecedingCharacter(params)); + this._parser.setCsiHandler('c', (params, collect) => this.sendDeviceAttributes(params, collect)); + this._parser.setCsiHandler('d', (params, collect) => this.linePosAbsolute(params)); + this._parser.setCsiHandler('e', (params, collect) => this.VPositionRelative(params)); + this._parser.setCsiHandler('f', (params, collect) => this.HVPosition(params)); + this._parser.setCsiHandler('g', (params, collect) => this.tabClear(params)); + this._parser.setCsiHandler('h', (params, collect) => this.setMode(params, collect)); + this._parser.setCsiHandler('l', (params, collect) => this.resetMode(params, collect)); + this._parser.setCsiHandler('m', (params, collect) => this.charAttributes(params, collect)); + this._parser.setCsiHandler('n', (params, collect) => this.deviceStatus(params, collect)); this._parser.setCsiHandler('p', (params, collect) => { if (collect === '!') { @@ -99,29 +94,29 @@ export class InputHandler implements IInputHandler { return this.setCursorStyle(params); } }); - this._parser.setCsiHandler('r', this.setScrollRegion.bind(this)); // fix collect - this._parser.setCsiHandler('s', this.saveCursor.bind(this)); - this._parser.setCsiHandler('u', this.restoreCursor.bind(this)); + this._parser.setCsiHandler('r', (params, collect) => this.setScrollRegion(params, collect)); + this._parser.setCsiHandler('s', (params, collect) => this.saveCursor(params)); + this._parser.setCsiHandler('u', (params, collect) => this.restoreCursor(params)); // execute handler - this._parser.setExecuteHandler(C0.BEL, this.bell.bind(this)); - this._parser.setExecuteHandler(C0.LF, this.lineFeed.bind(this)); - this._parser.setExecuteHandler(C0.VT, this.lineFeed.bind(this)); - this._parser.setExecuteHandler(C0.FF, this.lineFeed.bind(this)); - this._parser.setExecuteHandler(C0.CR, this.carriageReturn.bind(this)); - this._parser.setExecuteHandler(C0.BS, this.backspace.bind(this)); - this._parser.setExecuteHandler(C0.HT, this.tab.bind(this)); - this._parser.setExecuteHandler(C0.SO, this.shiftOut.bind(this)); - this._parser.setExecuteHandler(C0.SI, this.shiftIn.bind(this)); + this._parser.setExecuteHandler(C0.BEL, () => this.bell()); + this._parser.setExecuteHandler(C0.LF, () => this.lineFeed()); + this._parser.setExecuteHandler(C0.VT, () => this.lineFeed()); + this._parser.setExecuteHandler(C0.FF, () => this.lineFeed()); + this._parser.setExecuteHandler(C0.CR, () => this.carriageReturn()); + this._parser.setExecuteHandler(C0.BS, () => this.backspace()); + this._parser.setExecuteHandler(C0.HT, () => this.tab()); + this._parser.setExecuteHandler(C0.SO, () => this.shiftOut()); + this._parser.setExecuteHandler(C0.SI, () => this.shiftIn()); // 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.setOscHandler(0, this._terminal.handleTitle.bind(this._terminal)); + this._parser.setOscHandler(0, (data) => this._terminal.handleTitle(data)); // 1 - icon name // 2 - title - this._parser.setOscHandler(2, this._terminal.handleTitle.bind(this._terminal)); + this._parser.setOscHandler(2, (data) => this._terminal.handleTitle(data)); // 3 - set property X in the form "prop=value" // 4 - Change Color Number // 5 - Change Special Color Number @@ -156,15 +151,15 @@ export class InputHandler implements IInputHandler { // 119 - Reset highlight foreground color. // ESC handlers - this._parser.setEscHandler('', '7', this.saveCursor.bind(this)); - this._parser.setEscHandler('', '8', this.restoreCursor.bind(this)); - this._parser.setEscHandler('', 'D', this._terminal.index.bind(this._terminal)); + this._parser.setEscHandler('', '7', () => this.saveCursor([])); // fix args + this._parser.setEscHandler('', '8', () => this.restoreCursor([])); // fix args + this._parser.setEscHandler('', 'D', () => this._terminal.index()); this._parser.setEscHandler('', 'E', () => { this._terminal.buffer.x = 0; this._terminal.index(); }); - this._parser.setEscHandler('', 'H', this._terminal.tabSet.bind(this._terminal)); - this._parser.setEscHandler('', 'M', this._terminal.reverseIndex.bind(this._terminal)); + this._parser.setEscHandler('', 'H', () => this._terminal.tabSet()); + this._parser.setEscHandler('', 'M', () => this._terminal.reverseIndex()); this._parser.setEscHandler('', '=', () => { this._terminal.log('Serial port requested application keypad.'); this._terminal.applicationKeypad = true; @@ -179,7 +174,7 @@ export class InputHandler implements IInputHandler { this._terminal.viewport.syncScrollArea(); } }); - this._parser.setEscHandler('', 'c', this._terminal.reset.bind(this._terminal)); + this._parser.setEscHandler('', 'c', () => this._terminal.reset()); this._parser.setEscHandler('', 'n', () => this._terminal.setgLevel(2)); this._parser.setEscHandler('', 'o', () => this._terminal.setgLevel(3)); this._parser.setEscHandler('', '|', () => this._terminal.setgLevel(3)); @@ -918,18 +913,18 @@ export class InputHandler implements IInputHandler { * xterm/charproc.c - line 2012, for more information. * vim responds with ^[[?0c or ^[[?1c after the terminal's response (?) */ - public sendDeviceAttributes(params: number[]): void { + public sendDeviceAttributes(params: number[], collect?: string): void { if (params[0] > 0) { return; } - if (!this._terminal.prefix) { + if (!collect) { if (this._terminal.is('xterm') || this._terminal.is('rxvt-unicode') || this._terminal.is('screen')) { this._terminal.send(C0.ESC + '[?1;2c'); } else if (this._terminal.is('linux')) { this._terminal.send(C0.ESC + '[?6c'); } - } else if (this._terminal.prefix === '>') { + } else if (collect === '>') { // xterm and urxvt // seem to spit this // out around ~370 times (?). @@ -1105,7 +1100,7 @@ export class InputHandler implements IInputHandler { * Modes: * http: *vt100.net/docs/vt220-rm/chapter4.html */ - public setMode(params: number[]): void { + public setMode(params: number[], collect?: string): void { if (params.length > 1) { for (let i = 0; i < params.length; i++) { this.setMode([params[i]]); @@ -1114,7 +1109,7 @@ export class InputHandler implements IInputHandler { return; } - if (!this._terminal.prefix) { + if (!collect) { switch (params[0]) { case 4: this._terminal.insertMode = true; @@ -1123,7 +1118,7 @@ export class InputHandler implements IInputHandler { // this._t.convertEol = true; break; } - } else if (this._terminal.prefix === '?') { + } else if (collect === '?') { switch (params[0]) { case 1: this._terminal.applicationCursor = true; @@ -1299,7 +1294,7 @@ export class InputHandler implements IInputHandler { * Ps = 1 0 6 1 -> Reset keyboard emulation to Sun/PC style. * Ps = 2 0 0 4 -> Reset bracketed paste mode. */ - public resetMode(params: number[]): void { + public resetMode(params: number[], collect?: string): void { if (params.length > 1) { for (let i = 0; i < params.length; i++) { this.resetMode([params[i]]); @@ -1308,7 +1303,7 @@ export class InputHandler implements IInputHandler { return; } - if (!this._terminal.prefix) { + if (!collect) { switch (params[0]) { case 4: this._terminal.insertMode = false; @@ -1317,7 +1312,7 @@ export class InputHandler implements IInputHandler { // this._t.convertEol = false; break; } - } else if (this._terminal.prefix === '?') { + } else if (collect === '?') { switch (params[0]) { case 1: this._terminal.applicationCursor = false; @@ -1454,7 +1449,7 @@ export class InputHandler implements IInputHandler { * Ps = 4 8 ; 5 ; Ps -> Set background color to the second * Ps. */ - public charAttributes(params: number[]): void { + public charAttributes(params: number[], collect?: string): void { // Optimize a single SGR0. if (params.length === 1 && params[0] === 0) { this._terminal.curAttr = this._terminal.defAttr; @@ -1597,8 +1592,8 @@ export class InputHandler implements IInputHandler { * CSI ? 5 3 n Locator available, if compiled-in, or * CSI ? 5 0 n No Locator, if not. */ - public deviceStatus(params: number[]): void { - if (!this._terminal.prefix) { + public deviceStatus(params: number[], collect?: string): void { + if (!collect) { switch (params[0]) { case 5: // status report @@ -1613,7 +1608,7 @@ export class InputHandler implements IInputHandler { + 'R'); break; } - } else if (this._terminal.prefix === '?') { + } else if (collect === '?') { // modern xterm doesnt seem to // respond to any of these except ?6, 6, and 5 switch (params[0]) { @@ -1702,8 +1697,8 @@ export class InputHandler implements IInputHandler { * dow) (DECSTBM). * CSI ? Pm r */ - public setScrollRegion(params: number[]): void { - if (this._terminal.prefix) return; + public setScrollRegion(params: number[], collect?: string): void { + if (collect) return; this._terminal.buffer.scrollTop = (params[0] || 1) - 1; this._terminal.buffer.scrollBottom = (params[1] && params[1] <= this._terminal.rows ? params[1] : this._terminal.rows) - 1; this._terminal.buffer.x = 0; diff --git a/src/Terminal.ts b/src/Terminal.ts index 078266d1f6..c466396ea0 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -193,8 +193,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II public params: (string | number)[]; public currentParam: string | number; - public prefix: string; - public postfix: string; // user input states public writeBuffer: string[]; @@ -318,8 +316,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.params = []; this.currentParam = 0; - this.prefix = ''; - this.postfix = ''; // user input states this.writeBuffer = []; @@ -330,7 +326,6 @@ 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 08e9f59268..ecf5fb5d8f 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -45,7 +45,6 @@ export interface IInputHandlingTerminal extends IEventEmitter { bracketedPasteMode: boolean; defAttr: number; curAttr: number; - prefix: string; savedCols: number; x10Mouse: boolean; vt200Mouse: boolean; @@ -427,50 +426,6 @@ export interface IParsingState { 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 @@ -483,13 +438,6 @@ export interface IDcsHandler { unhook(): void; } -/** -* Error handler signature for EscapeSequenceParser. -*/ -export interface IErrorHandler { - (state: IParsingState): IParsingState; -} - /** * EscapeSequenceParser interface. */ @@ -497,22 +445,22 @@ export interface IEscapeSequenceParser { reset(): void; parse(data: string): void; - setPrintHandler(callback: IPrintHandler): void; + setPrintHandler(callback: (data: string, start: number, end: number) => void): void; clearPrintHandler(): void; - setExecuteHandler(flag: string, callback: IExecuteHandler): void; + setExecuteHandler(flag: string, callback: () => void): void; clearExecuteHandler(flag: string): void; setExecuteHandlerFallback(callback: (...params: any[]) => void): void; - setCsiHandler(flag: string, callback: ICsiHandler): void; + setCsiHandler(flag: string, callback: (params: number[], collect: string) => void): void; clearCsiHandler(flag: string): void; setCsiHandlerFallback(callback: (...params: any[]) => void): void; - setEscHandler(collect: string, flag: string, callback: IEscHandler): void; + setEscHandler(collect: string, flag: string, callback: (collect: string, flag: number) => void): void; clearEscHandler(collect: string, flag: string): void; setEscHandlerFallback(callback: (...params: any[]) => void): void; - setOscHandler(ident: number, callback: IOscHandler): void; + setOscHandler(ident: number, callback: (data: string) => void): void; clearOscHandler(ident: number): void; setOscHandlerFallback(callback: (...params: any[]) => void): void; @@ -520,9 +468,6 @@ export interface IEscapeSequenceParser { clearDcsHandler(collect: string, flag: string): void; setDcsHandlerFallback(handler: IDcsHandler): void; - setErrorHandler(callback: IErrorHandler): void; + setErrorHandler(callback: (state: IParsingState) => IParsingState): void; clearErrorHandler(): void; - - // remove after revamp of InputHandler methods - setPrefixHandler(callback: (collect: string) => void): void; } diff --git a/src/utils/TestUtils.test.ts b/src/utils/TestUtils.test.ts index 51aafb50f7..e3ae89d303 100644 --- a/src/utils/TestUtils.test.ts +++ b/src/utils/TestUtils.test.ts @@ -184,7 +184,6 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal { bracketedPasteMode: boolean; defAttr: number; curAttr: number; - prefix: string; savedCols: number; x10Mouse: boolean; vt200Mouse: boolean; From 0535f5d1b3e0734fa5c700e4cdc0a5b46e9d599d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 1 May 2018 14:58:16 +0200 Subject: [PATCH 24/44] unified method signature in InputHandler, moved some CSI condition into methods (FIXME: needs intermediate handler) --- src/InputHandler.test.ts | 15 ++-- src/InputHandler.ts | 168 ++++++++++++++++++--------------------- src/Types.ts | 72 ++++++++--------- 3 files changed, 123 insertions(+), 132 deletions(-) diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index 2ecec89a8d..ddb1678614 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -29,38 +29,39 @@ describe('InputHandler', () => { it('should call Terminal.setOption with correct params', () => { let terminal = new MockInputHandlingTerminal(); let inputHandler = new InputHandler(terminal); + const collect = ' '; - inputHandler.setCursorStyle([0]); + inputHandler.setCursorStyle([0], collect); assert.equal(terminal.options['cursorStyle'], 'block'); assert.equal(terminal.options['cursorBlink'], true); terminal.options = {}; - inputHandler.setCursorStyle([1]); + inputHandler.setCursorStyle([1], collect); assert.equal(terminal.options['cursorStyle'], 'block'); assert.equal(terminal.options['cursorBlink'], true); terminal.options = {}; - inputHandler.setCursorStyle([2]); + inputHandler.setCursorStyle([2], collect); assert.equal(terminal.options['cursorStyle'], 'block'); assert.equal(terminal.options['cursorBlink'], false); terminal.options = {}; - inputHandler.setCursorStyle([3]); + inputHandler.setCursorStyle([3], collect); assert.equal(terminal.options['cursorStyle'], 'underline'); assert.equal(terminal.options['cursorBlink'], true); terminal.options = {}; - inputHandler.setCursorStyle([4]); + inputHandler.setCursorStyle([4], collect); assert.equal(terminal.options['cursorStyle'], 'underline'); assert.equal(terminal.options['cursorBlink'], false); terminal.options = {}; - inputHandler.setCursorStyle([5]); + inputHandler.setCursorStyle([5], collect); assert.equal(terminal.options['cursorStyle'], 'bar'); assert.equal(terminal.options['cursorBlink'], true); terminal.options = {}; - inputHandler.setCursorStyle([6]); + inputHandler.setCursorStyle([6], collect); assert.equal(terminal.options['cursorStyle'], 'bar'); assert.equal(terminal.options['cursorBlink'], false); }); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 8a614f1703..c85a2b94c5 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -42,8 +42,7 @@ export class InputHandler implements IInputHandler { }); // print handler - this._parser.setPrintHandler( - (data: string, start: number, end: number): void => this.print(data, start, end)); + this._parser.setPrintHandler((data, start, end): void => this.print(data, start, end)); // CSI handler this._parser.setCsiHandler('@', (params, collect) => this.insertChars(params)); @@ -62,12 +61,7 @@ export class InputHandler implements IInputHandler { this._parser.setCsiHandler('M', (params, collect) => this.deleteLines(params)); this._parser.setCsiHandler('P', (params, collect) => this.deleteChars(params)); this._parser.setCsiHandler('S', (params, collect) => this.scrollUp(params)); - this._parser.setCsiHandler('T', - (params, collect) => { - if (params.length < 2 && !collect) { - return this.scrollDown(params); - } - }); + this._parser.setCsiHandler('T', (params, collect) => this.scrollDown(params, collect)); this._parser.setCsiHandler('X', (params, collect) => this.eraseChars(params)); this._parser.setCsiHandler('Z', (params, collect) => this.cursorBackwardTab(params)); this._parser.setCsiHandler('`', (params, collect) => this.charPosAbsolute(params)); @@ -82,18 +76,8 @@ export class InputHandler implements IInputHandler { this._parser.setCsiHandler('l', (params, collect) => this.resetMode(params, collect)); this._parser.setCsiHandler('m', (params, collect) => this.charAttributes(params, collect)); this._parser.setCsiHandler('n', (params, collect) => this.deviceStatus(params, collect)); - this._parser.setCsiHandler('p', - (params, collect) => { - if (collect === '!') { - return this.softReset(params); - } - }); - this._parser.setCsiHandler('q', - (params, collect) => { - if (collect === ' ') { - return this.setCursorStyle(params); - } - }); + this._parser.setCsiHandler('p', (params, collect) => this.softReset(params, collect)); + this._parser.setCsiHandler('q', (params, collect) => this.setCursorStyle(params, collect)); this._parser.setCsiHandler('r', (params, collect) => this.setScrollRegion(params, collect)); this._parser.setCsiHandler('s', (params, collect) => this.saveCursor(params)); this._parser.setCsiHandler('u', (params, collect) => this.restoreCursor(params)); @@ -428,7 +412,7 @@ export class InputHandler implements IInputHandler { * CSI Ps @ * Insert Ps (Blank) Character(s) (default = 1) (ICH). */ - public insertChars(params: number[]): void { + public insertChars(params: number[], collect?: string): void { let param = params[0]; if (param < 1) param = 1; @@ -449,7 +433,7 @@ export class InputHandler implements IInputHandler { * CSI Ps A * Cursor Up Ps Times (default = 1) (CUU). */ - public cursorUp(params: number[]): void { + public cursorUp(params: number[], collect?: string): void { let param = params[0]; if (param < 1) { param = 1; @@ -464,7 +448,7 @@ export class InputHandler implements IInputHandler { * CSI Ps B * Cursor Down Ps Times (default = 1) (CUD). */ - public cursorDown(params: number[]): void { + public cursorDown(params: number[], collect?: string): void { let param = params[0]; if (param < 1) { param = 1; @@ -483,7 +467,7 @@ export class InputHandler implements IInputHandler { * CSI Ps C * Cursor Forward Ps Times (default = 1) (CUF). */ - public cursorForward(params: number[]): void { + public cursorForward(params: number[], collect?: string): void { let param = params[0]; if (param < 1) { param = 1; @@ -498,7 +482,7 @@ export class InputHandler implements IInputHandler { * CSI Ps D * Cursor Backward Ps Times (default = 1) (CUB). */ - public cursorBackward(params: number[]): void { + public cursorBackward(params: number[], collect?: string): void { let param = params[0]; if (param < 1) { param = 1; @@ -518,7 +502,7 @@ export class InputHandler implements IInputHandler { * Cursor Next Line Ps Times (default = 1) (CNL). * same as CSI Ps B ? */ - public cursorNextLine(params: number[]): void { + public cursorNextLine(params: number[], collect?: string): void { let param = params[0]; if (param < 1) { param = 1; @@ -536,7 +520,7 @@ export class InputHandler implements IInputHandler { * Cursor Preceding Line Ps Times (default = 1) (CNL). * reuse CSI Ps A ? */ - public cursorPrecedingLine(params: number[]): void { + public cursorPrecedingLine(params: number[], collect?: string): void { let param = params[0]; if (param < 1) { param = 1; @@ -553,7 +537,7 @@ export class InputHandler implements IInputHandler { * CSI Ps G * Cursor Character Absolute [column] (default = [row,1]) (CHA). */ - public cursorCharAbsolute(params: number[]): void { + public cursorCharAbsolute(params: number[], collect?: string): void { let param = params[0]; if (param < 1) { param = 1; @@ -565,7 +549,7 @@ export class InputHandler implements IInputHandler { * CSI Ps ; Ps H * Cursor Position [row;column] (default = [1,1]) (CUP). */ - public cursorPosition(params: number[]): void { + public cursorPosition(params: number[], collect?: string): void { let col: number; let row: number = params[0] - 1; @@ -595,7 +579,7 @@ export class InputHandler implements IInputHandler { * CSI Ps I * Cursor Forward Tabulation Ps tab stops (default = 1) (CHT). */ - public cursorForwardTab(params: number[]): void { + public cursorForwardTab(params: number[], collect?: string): void { let param = params[0] || 1; while (param--) { this._terminal.buffer.x = this._terminal.buffer.nextStop(); @@ -614,7 +598,7 @@ export class InputHandler implements IInputHandler { * Ps = 1 -> Selective Erase Above. * Ps = 2 -> Selective Erase All. */ - public eraseInDisplay(params: number[]): void { + public eraseInDisplay(params: number[], collect?: string): void { let j; switch (params[0]) { case 0: @@ -660,7 +644,7 @@ export class InputHandler implements IInputHandler { * Ps = 1 -> Selective Erase to Left. * Ps = 2 -> Selective Erase All. */ - public eraseInLine(params: number[]): void { + public eraseInLine(params: number[], collect?: string): void { switch (params[0]) { case 0: this._terminal.eraseRight(this._terminal.buffer.x, this._terminal.buffer.y); @@ -678,7 +662,7 @@ export class InputHandler implements IInputHandler { * CSI Ps L * Insert Ps Line(s) (default = 1) (IL). */ - public insertLines(params: number[]): void { + public insertLines(params: number[], collect?: string): void { let param: number = params[0]; if (param < 1) { param = 1; @@ -707,7 +691,7 @@ export class InputHandler implements IInputHandler { * CSI Ps M * Delete Ps Line(s) (default = 1) (DL). */ - public deleteLines(params: number[]): void { + public deleteLines(params: number[], collect?: string): void { let param = params[0]; if (param < 1) { param = 1; @@ -737,7 +721,7 @@ export class InputHandler implements IInputHandler { * CSI Ps P * Delete Ps Character(s) (default = 1) (DCH). */ - public deleteChars(params: number[]): void { + public deleteChars(params: number[], collect?: string): void { let param: number = params[0]; if (param < 1) { param = 1; @@ -759,7 +743,7 @@ export class InputHandler implements IInputHandler { /** * CSI Ps S Scroll up Ps lines (default = 1) (SU). */ - public scrollUp(params: number[]): void { + public scrollUp(params: number[], collect?: string): void { let param = params[0] || 1; // make buffer local for faster access @@ -777,26 +761,28 @@ export class InputHandler implements IInputHandler { /** * CSI Ps T Scroll down Ps lines (default = 1) (SD). */ - public scrollDown(params: number[]): void { - let param = params[0] || 1; + public scrollDown(params: number[], collect?: string): void { + if (params.length < 2 && !collect) { + let param = params[0] || 1; - // make buffer local for faster access - const buffer = this._terminal.buffer; + // make buffer local for faster access + const buffer = this._terminal.buffer; - while (param--) { - buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 1); - buffer.lines.splice(buffer.ybase + buffer.scrollTop, 0, this._terminal.blankLine()); + while (param--) { + buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 1); + buffer.lines.splice(buffer.ybase + buffer.scrollTop, 0, this._terminal.blankLine()); + } + // this.maxRange(); + this._terminal.updateRange(buffer.scrollTop); + this._terminal.updateRange(buffer.scrollBottom); } - // this.maxRange(); - this._terminal.updateRange(buffer.scrollTop); - this._terminal.updateRange(buffer.scrollBottom); } /** * CSI Ps X * Erase Ps Character(s) (default = 1) (ECH). */ - public eraseChars(params: number[]): void { + public eraseChars(params: number[], collect?: string): void { let param = params[0]; if (param < 1) { param = 1; @@ -817,7 +803,7 @@ export class InputHandler implements IInputHandler { /** * CSI Ps Z Cursor Backward Tabulation Ps tab stops (default = 1) (CBT). */ - public cursorBackwardTab(params: number[]): void { + public cursorBackwardTab(params: number[], collect?: string): void { let param = params[0] || 1; // make buffer local for faster access @@ -832,7 +818,7 @@ export class InputHandler implements IInputHandler { * CSI Pm ` Character Position Absolute * [column] (default = [row,1]) (HPA). */ - public charPosAbsolute(params: number[]): void { + public charPosAbsolute(params: number[], collect?: string): void { let param = params[0]; if (param < 1) { param = 1; @@ -848,7 +834,7 @@ export class InputHandler implements IInputHandler { * [columns] (default = [row,col+1]) (HPR) * reuse CSI Ps C ? */ - public HPositionRelative(params: number[]): void { + public HPositionRelative(params: number[], collect?: string): void { let param = params[0]; if (param < 1) { param = 1; @@ -862,7 +848,7 @@ export class InputHandler implements IInputHandler { /** * CSI Ps b Repeat the preceding graphic character Ps times (REP). */ - public repeatPrecedingCharacter(params: number[]): void { + public repeatPrecedingCharacter(params: number[], collect?: string): void { let param = params[0] || 1; // make buffer local for faster access @@ -946,7 +932,7 @@ export class InputHandler implements IInputHandler { * CSI Pm d Vertical Position Absolute (VPA) * [row] (default = [1,column]) */ - public linePosAbsolute(params: number[]): void { + public linePosAbsolute(params: number[], collect?: string): void { let param = params[0]; if (param < 1) { param = 1; @@ -962,7 +948,7 @@ export class InputHandler implements IInputHandler { * [rows] (default = [row+1,column]) * reuse CSI Ps B ? */ - public VPositionRelative(params: number[]): void { + public VPositionRelative(params: number[], collect?: string): void { let param = params[0]; if (param < 1) { param = 1; @@ -982,7 +968,7 @@ export class InputHandler implements IInputHandler { * Horizontal and Vertical Position [row;column] (default = * [1,1]) (HVP). */ - public HVPosition(params: number[]): void { + public HVPosition(params: number[], collect?: string): void { if (params[0] < 1) params[0] = 1; if (params[1] < 1) params[1] = 1; @@ -1005,7 +991,7 @@ export class InputHandler implements IInputHandler { * Ps = 2 -> Clear Stops on Line. * http://vt100.net/annarbor/aaa-ug/section6.html */ - public tabClear(params: number[]): void { + public tabClear(params: number[], collect?: string): void { let param = params[0]; if (param <= 0) { delete this._terminal.buffer.tabs[this._terminal.buffer.x]; @@ -1644,21 +1630,23 @@ export class InputHandler implements IInputHandler { * CSI ! p Soft terminal reset (DECSTR). * http://vt100.net/docs/vt220-rm/table4-10.html */ - public softReset(params: number[]): void { - this._terminal.cursorHidden = false; - this._terminal.insertMode = false; - this._terminal.originMode = false; - this._terminal.wraparoundMode = true; // defaults: xterm - true, vt100 - false - this._terminal.applicationKeypad = false; // ? - this._terminal.viewport.syncScrollArea(); - this._terminal.applicationCursor = false; - this._terminal.buffer.scrollTop = 0; - this._terminal.buffer.scrollBottom = this._terminal.rows - 1; - this._terminal.curAttr = this._terminal.defAttr; - this._terminal.buffer.x = this._terminal.buffer.y = 0; // ? - this._terminal.charset = null; - this._terminal.glevel = 0; // ?? - this._terminal.charsets = [null]; // ?? + public softReset(params: number[], collect?: string): void { + if (collect === '!') { + this._terminal.cursorHidden = false; + this._terminal.insertMode = false; + this._terminal.originMode = false; + this._terminal.wraparoundMode = true; // defaults: xterm - true, vt100 - false + this._terminal.applicationKeypad = false; // ? + this._terminal.viewport.syncScrollArea(); + this._terminal.applicationCursor = false; + this._terminal.buffer.scrollTop = 0; + this._terminal.buffer.scrollBottom = this._terminal.rows - 1; + this._terminal.curAttr = this._terminal.defAttr; + this._terminal.buffer.x = this._terminal.buffer.y = 0; // ? + this._terminal.charset = null; + this._terminal.glevel = 0; // ?? + this._terminal.charsets = [null]; // ?? + } } /** @@ -1671,24 +1659,26 @@ export class InputHandler implements IInputHandler { * Ps = 5 -> blinking bar (xterm). * Ps = 6 -> steady bar (xterm). */ - public setCursorStyle(params?: number[]): void { - const param = params[0] < 1 ? 1 : params[0]; - switch (param) { - case 1: - case 2: - this._terminal.setOption('cursorStyle', 'block'); - break; - case 3: - case 4: - this._terminal.setOption('cursorStyle', 'underline'); - break; - case 5: - case 6: - this._terminal.setOption('cursorStyle', 'bar'); - break; + public setCursorStyle(params?: number[], collect?: string): void { + if (collect === ' ') { + const param = params[0] < 1 ? 1 : params[0]; + switch (param) { + case 1: + case 2: + this._terminal.setOption('cursorStyle', 'block'); + break; + case 3: + case 4: + this._terminal.setOption('cursorStyle', 'underline'); + break; + case 5: + case 6: + this._terminal.setOption('cursorStyle', 'bar'); + break; + } + const isBlinking = param % 2 === 1; + this._terminal.setOption('cursorBlink', isBlinking); } - const isBlinking = param % 2 === 1; - this._terminal.setOption('cursorBlink', isBlinking); } /** @@ -1710,7 +1700,7 @@ export class InputHandler implements IInputHandler { * CSI s * Save cursor (ANSI.SYS). */ - public saveCursor(params: number[]): void { + public saveCursor(params: number[], collect?: string): void { this._terminal.buffer.savedX = this._terminal.buffer.x; this._terminal.buffer.savedY = this._terminal.buffer.y; } @@ -1720,7 +1710,7 @@ export class InputHandler implements IInputHandler { * CSI u * Restore cursor (ANSI.SYS). */ - public restoreCursor(params: number[]): void { + public restoreCursor(params: number[], collect?: string): void { this._terminal.buffer.x = this._terminal.buffer.savedX || 0; this._terminal.buffer.y = this._terminal.buffer.savedY || 0; } diff --git a/src/Types.ts b/src/Types.ts index ecf5fb5d8f..6d48eaac42 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -119,42 +119,42 @@ export interface IInputHandler { /** C0 SO */ shiftOut(): void; /** C0 SI */ shiftIn(): void; - /** CSI @ */ insertChars(params?: number[]): void; - /** CSI A */ cursorUp(params?: number[]): void; - /** CSI B */ cursorDown(params?: number[]): void; - /** CSI C */ cursorForward(params?: number[]): void; - /** CSI D */ cursorBackward(params?: number[]): void; - /** CSI E */ cursorNextLine(params?: number[]): void; - /** CSI F */ cursorPrecedingLine(params?: number[]): void; - /** CSI G */ cursorCharAbsolute(params?: number[]): void; - /** CSI H */ cursorPosition(params?: number[]): void; - /** CSI I */ cursorForwardTab(params?: number[]): void; - /** CSI J */ eraseInDisplay(params?: number[]): void; - /** CSI K */ eraseInLine(params?: number[]): void; - /** CSI L */ insertLines(params?: number[]): void; - /** CSI M */ deleteLines(params?: number[]): void; - /** CSI P */ deleteChars(params?: number[]): void; - /** CSI S */ scrollUp(params?: number[]): void; - /** CSI T */ scrollDown(params?: number[]): void; - /** CSI X */ eraseChars(params?: number[]): void; - /** CSI Z */ cursorBackwardTab(params?: number[]): void; - /** CSI ` */ charPosAbsolute(params?: number[]): void; - /** CSI a */ HPositionRelative(params?: number[]): void; - /** CSI b */ repeatPrecedingCharacter(params?: number[]): void; - /** CSI c */ sendDeviceAttributes(params?: number[]): void; - /** CSI d */ linePosAbsolute(params?: number[]): void; - /** CSI e */ VPositionRelative(params?: number[]): void; - /** CSI f */ HVPosition(params?: number[]): void; - /** CSI g */ tabClear(params?: number[]): void; - /** CSI h */ setMode(params?: number[]): void; - /** CSI l */ resetMode(params?: number[]): void; - /** CSI m */ charAttributes(params?: number[]): void; - /** CSI n */ deviceStatus(params?: number[]): void; - /** CSI p */ softReset(params?: number[]): void; - /** CSI q */ setCursorStyle(params?: number[]): void; - /** CSI r */ setScrollRegion(params?: number[]): void; - /** CSI s */ saveCursor(params?: number[]): void; - /** CSI u */ restoreCursor(params?: number[]): void; + /** CSI @ */ insertChars(params?: number[], collect?: string): void; + /** CSI A */ cursorUp(params?: number[], collect?: string): void; + /** CSI B */ cursorDown(params?: number[], collect?: string): void; + /** CSI C */ cursorForward(params?: number[], collect?: string): void; + /** CSI D */ cursorBackward(params?: number[], collect?: string): void; + /** CSI E */ cursorNextLine(params?: number[], collect?: string): void; + /** CSI F */ cursorPrecedingLine(params?: number[], collect?: string): void; + /** CSI G */ cursorCharAbsolute(params?: number[], collect?: string): void; + /** CSI H */ cursorPosition(params?: number[], collect?: string): void; + /** CSI I */ cursorForwardTab(params?: number[], collect?: string): void; + /** CSI J */ eraseInDisplay(params?: number[], collect?: string): void; + /** CSI K */ eraseInLine(params?: number[], collect?: string): void; + /** CSI L */ insertLines(params?: number[], collect?: string): void; + /** CSI M */ deleteLines(params?: number[], collect?: string): void; + /** CSI P */ deleteChars(params?: number[], collect?: string): void; + /** CSI S */ scrollUp(params?: number[], collect?: string): void; + /** CSI T */ scrollDown(params?: number[], collect?: string): void; + /** CSI X */ eraseChars(params?: number[], collect?: string): void; + /** CSI Z */ cursorBackwardTab(params?: number[], collect?: string): void; + /** CSI ` */ charPosAbsolute(params?: number[], collect?: string): void; + /** CSI a */ HPositionRelative(params?: number[], collect?: string): void; + /** CSI b */ repeatPrecedingCharacter(params?: number[], collect?: string): void; + /** CSI c */ sendDeviceAttributes(params?: number[], collect?: string): void; + /** CSI d */ linePosAbsolute(params?: number[], collect?: string): void; + /** CSI e */ VPositionRelative(params?: number[], collect?: string): void; + /** CSI f */ HVPosition(params?: number[], collect?: string): void; + /** CSI g */ tabClear(params?: number[], collect?: string): void; + /** CSI h */ setMode(params?: number[], collect?: string): void; + /** CSI l */ resetMode(params?: number[], collect?: string): void; + /** CSI m */ charAttributes(params?: number[], collect?: string): void; + /** CSI n */ deviceStatus(params?: number[], collect?: string): void; + /** CSI p */ softReset(params?: number[], collect?: string): void; + /** CSI q */ setCursorStyle(params?: number[], collect?: string): void; + /** CSI r */ setScrollRegion(params?: number[], collect?: string): void; + /** CSI s */ saveCursor(params?: number[], collect?: string): void; + /** CSI u */ restoreCursor(params?: number[], collect?: string): void; } export interface ILinkMatcher { From 2eeb7b8ea5c737d9decb5d08695fef1a3ce27f65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 1 May 2018 16:36:12 +0200 Subject: [PATCH 25/44] ESC and OSC method entries for InputHandler --- src/InputHandler.ts | 187 +++++++++++++++++++++++++++++++------------- src/Types.ts | 14 ++++ 2 files changed, 147 insertions(+), 54 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index c85a2b94c5..8339a8beba 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -12,6 +12,11 @@ import { FLAGS } from './renderer/Types'; import { wcwidth } from './CharWidth'; import { EscapeSequenceParser } from './EscapeSequenceParser'; +/** + * Map collect to glevel. Used in `selectCharset`. + */ +const GLEVEL = {'(': 0, ')': 1, '*': 2, '+': 3, '-': 1, '.': 2}; + /** * The terminal's standard implementation of IInputHandler, this handles all * input from the Parser. @@ -23,11 +28,13 @@ export class InputHandler implements IInputHandler { private _parser: EscapeSequenceParser; private _surrogateHigh: string; - constructor(protected _terminal: any) { + constructor(private _terminal: any) { this._parser = new EscapeSequenceParser; // FIXME: maybe as ctor argument this._surrogateHigh = ''; - // custom fallback handlers + /** + * custom fallback handlers + */ this._parser.setCsiHandlerFallback((...params: any[]) => { this._terminal.error('Unknown CSI code: ', params); }); @@ -41,10 +48,14 @@ export class InputHandler implements IInputHandler { this._terminal.error('Unknown OSC code: ', params); }); - // print handler + /** + * print handler + */ this._parser.setPrintHandler((data, start, end): void => this.print(data, start, end)); - // CSI handler + /** + * CSI handler + */ this._parser.setCsiHandler('@', (params, collect) => this.insertChars(params)); this._parser.setCsiHandler('A', (params, collect) => this.cursorUp(params)); this._parser.setCsiHandler('B', (params, collect) => this.cursorDown(params)); @@ -82,7 +93,9 @@ export class InputHandler implements IInputHandler { this._parser.setCsiHandler('s', (params, collect) => this.saveCursor(params)); this._parser.setCsiHandler('u', (params, collect) => this.restoreCursor(params)); - // execute handler + /** + * execute handler + */ this._parser.setExecuteHandler(C0.BEL, () => this.bell()); this._parser.setExecuteHandler(C0.LF, () => this.lineFeed()); this._parser.setExecuteHandler(C0.VT, () => this.lineFeed()); @@ -95,12 +108,14 @@ export class InputHandler implements IInputHandler { // FIXME: What do to with missing? Old code just added those to print, but that's wrong // behavior for most control codes. - // OSC handler + /** + * OSC handler + */ // 0 - icon name + title - this._parser.setOscHandler(0, (data) => this._terminal.handleTitle(data)); + this._parser.setOscHandler(0, (data) => this.setTitle(data)); // 1 - icon name // 2 - title - this._parser.setOscHandler(2, (data) => this._terminal.handleTitle(data)); + this._parser.setOscHandler(2, (data) => this.setTitle(data)); // 3 - set property X in the form "prop=value" // 4 - Change Color Number // 5 - Change Special Color Number @@ -134,55 +149,38 @@ export class InputHandler implements IInputHandler { // 118 - Reset Tektronix cursor color. // 119 - Reset highlight foreground color. - // ESC handlers - this._parser.setEscHandler('', '7', () => this.saveCursor([])); // fix args - this._parser.setEscHandler('', '8', () => this.restoreCursor([])); // fix args - this._parser.setEscHandler('', 'D', () => this._terminal.index()); - this._parser.setEscHandler('', 'E', () => { - this._terminal.buffer.x = 0; - this._terminal.index(); - }); - this._parser.setEscHandler('', 'H', () => this._terminal.tabSet()); - this._parser.setEscHandler('', 'M', () => this._terminal.reverseIndex()); - this._parser.setEscHandler('', '=', () => { - this._terminal.log('Serial port requested application keypad.'); - this._terminal.applicationKeypad = true; - if (this._terminal.viewport) { - this._terminal.viewport.syncScrollArea(); - } - }); - this._parser.setEscHandler('', '>', () => { - this._terminal.log('Switching back to normal keypad.'); - this._terminal.applicationKeypad = false; - if (this._terminal.viewport) { - this._terminal.viewport.syncScrollArea(); - } - }); - this._parser.setEscHandler('', 'c', () => this._terminal.reset()); - this._parser.setEscHandler('', 'n', () => this._terminal.setgLevel(2)); - this._parser.setEscHandler('', 'o', () => this._terminal.setgLevel(3)); - this._parser.setEscHandler('', '|', () => this._terminal.setgLevel(3)); - this._parser.setEscHandler('', '}', () => this._terminal.setgLevel(2)); - this._parser.setEscHandler('', '~', () => this._terminal.setgLevel(1)); - - this._parser.setEscHandler('%', '@', () => { - this._terminal.setgLevel(0); - this._terminal.setgCharset(0, DEFAULT_CHARSET); // US (default) - }); - this._parser.setEscHandler('%', 'G', () => { - this._terminal.setgLevel(0); - this._terminal.setgCharset(0, DEFAULT_CHARSET); // US (default) - }); + /** + * ESC handlers + */ + this._parser.setEscHandler('', '7', () => this.saveCursor([])); + this._parser.setEscHandler('', '8', () => this.restoreCursor([])); + this._parser.setEscHandler('', 'D', () => this._terminal.index()); // FIXME: move? + this._parser.setEscHandler('', 'E', () => this.nextLine()); + this._parser.setEscHandler('', 'H', () => this._terminal.tabSet()); // FIXME: move? + this._parser.setEscHandler('', 'M', () => this._terminal.reverseIndex()); // FIXME: move? + this._parser.setEscHandler('', '=', () => this.keypadApplicationMode()); + this._parser.setEscHandler('', '>', () => this.keypadNumericMode()); + this._parser.setEscHandler('', 'c', () => this._terminal.reset()); // FIXME: move? + this._parser.setEscHandler('', 'n', () => this._terminal.setgLevel(2)); // FIXME: move? + this._parser.setEscHandler('', 'o', () => this._terminal.setgLevel(3)); // FIXME: move? + this._parser.setEscHandler('', '|', () => this._terminal.setgLevel(3)); // FIXME: move? + this._parser.setEscHandler('', '}', () => this._terminal.setgLevel(2)); // FIXME: move? + this._parser.setEscHandler('', '~', () => this._terminal.setgLevel(1)); // FIXME: move? + this._parser.setEscHandler('%', '@', () => this.selectDefaultCharset()); + this._parser.setEscHandler('%', 'G', () => this.selectDefaultCharset()); for (let flag in CHARSETS) { - this._parser.setEscHandler('(', flag, () => this._terminal.setgCharset(0, CHARSETS[flag] || DEFAULT_CHARSET)); - this._parser.setEscHandler(')', flag, () => this._terminal.setgCharset(1, CHARSETS[flag] || DEFAULT_CHARSET)); - this._parser.setEscHandler('*', flag, () => this._terminal.setgCharset(2, CHARSETS[flag] || DEFAULT_CHARSET)); - this._parser.setEscHandler('+', flag, () => this._terminal.setgCharset(3, CHARSETS[flag] || DEFAULT_CHARSET)); - this._parser.setEscHandler('-', flag, () => this._terminal.setgCharset(1, CHARSETS[flag] || DEFAULT_CHARSET)); - this._parser.setEscHandler('.', flag, () => this._terminal.setgCharset(2, CHARSETS[flag] || DEFAULT_CHARSET)); + this._parser.setEscHandler('(', flag, () => this.selectCharset('(' + flag)); + this._parser.setEscHandler(')', flag, () => this.selectCharset(')' + flag)); + this._parser.setEscHandler('*', flag, () => this.selectCharset('*' + flag)); + this._parser.setEscHandler('+', flag, () => this.selectCharset('+' + flag)); + this._parser.setEscHandler('-', flag, () => this.selectCharset('-' + flag)); + this._parser.setEscHandler('.', flag, () => this.selectCharset('.' + flag)); + this._parser.setEscHandler('/', flag, () => this.selectCharset('/' + flag)); // FIXME } - // error handler + /** + * error handler + */ this._parser.setErrorHandler((state) => { this._terminal.error('Parsing error: ', state); return state; @@ -1698,6 +1696,7 @@ export class InputHandler implements IInputHandler { /** * CSI s + * ESC 7 * Save cursor (ANSI.SYS). */ public saveCursor(params: number[], collect?: string): void { @@ -1708,10 +1707,90 @@ export class InputHandler implements IInputHandler { /** * CSI u + * ESC 8 * Restore cursor (ANSI.SYS). */ public restoreCursor(params: number[], collect?: string): void { this._terminal.buffer.x = this._terminal.buffer.savedX || 0; this._terminal.buffer.y = this._terminal.buffer.savedY || 0; } + + + /** + * OSC 0; ST (set icon name + window title) + * OSC 2; ST (set icon name) + * Proxy to set window title. Icon name is not supported. + */ + public setTitle(data: string): void { + this._terminal.handleTitle(data); + } + + /** + * ESC E + * DEC mnemonic: NEL (https://vt100.net/docs/vt510-rm/NEL) + * Moves cursor to first position on next line. + */ + public nextLine(): void { + this._terminal.buffer.x = 0; + this._terminal.index(); + } + + /** + * ESC = + * DEC mnemonic: DECKPAM (https://vt100.net/docs/vt510-rm/DECKPAM.html) + * Enables the numeric keypad to send application sequences to the host. + */ + public keypadApplicationMode(): void { + this._terminal.log('Serial port requested application keypad.'); + this._terminal.applicationKeypad = true; + if (this._terminal.viewport) { + this._terminal.viewport.syncScrollArea(); + } + } + + /** + * ESC > + * DEC mnemonic: DECKPNM (https://vt100.net/docs/vt510-rm/DECKPNM.html) + * Enables the keypad to send numeric characters to the host. + */ + public keypadNumericMode(): void { + this._terminal.log('Switching back to normal keypad.'); + this._terminal.applicationKeypad = false; + if (this._terminal.viewport) { + this._terminal.viewport.syncScrollArea(); + } + } + + /** + * ESC % @ + * ESC % G + * Select default character set. UTF-8 is not supported (string are unicode anyways) + * therefore ESC % G does the same. + */ + public selectDefaultCharset(): void { + this._terminal.setgLevel(0); + this._terminal.setgCharset(0, DEFAULT_CHARSET); // US (default) + } + + /** + * ESC ( C + * Designate G0 Character Set, VT100, ISO 2022. + * ESC ) C + * Designate G1 Character Set (ISO 2022, VT100). + * ESC * C + * Designate G2 Character Set (ISO 2022, VT220). + * ESC + C + * Designate G3 Character Set (ISO 2022, VT220). + * ESC - C + * Designate G1 Character Set (VT300). + * ESC . C + * Designate G2 Character Set (VT300). + * ESC / C + * Designate G3 Character Set (VT300). C = A -> ISO Latin-1 Supplemental. - Supported? + */ + public selectCharset(collectAndFlag: string): void { + if (collectAndFlag.length !== 2) return this.selectDefaultCharset(); + if (collectAndFlag[0] === '/') return; // FIXME: Is this supported? + this._terminal.setgCharset(GLEVEL[collectAndFlag[0]], CHARSETS[collectAndFlag[1]] || DEFAULT_CHARSET); + } } diff --git a/src/Types.ts b/src/Types.ts index 6d48eaac42..59efe71f34 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -155,6 +155,20 @@ export interface IInputHandler { /** CSI r */ setScrollRegion(params?: number[], collect?: string): void; /** CSI s */ saveCursor(params?: number[], collect?: string): void; /** CSI u */ restoreCursor(params?: number[], collect?: string): void; + /** OSC 0 + OSC 2 */ setTitle(data: string): void; + /** ESC E */ nextLine(): void; + /** ESC = */ keypadApplicationMode(): void; + /** ESC > */ keypadNumericMode(): void; + /** ESC % G + ESC % @ */ selectDefaultCharset(): void; + /** ESC ( C + * ESC ) C + * ESC * C + * ESC + C + * ESC - C + * ESC . C + * ESC / C */ selectCharset(collectAndFlag: string): void; } export interface ILinkMatcher { From c7fdff2f40a77f0ae2b0c19556086dd53cc06dfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 1 May 2018 17:50:41 +0200 Subject: [PATCH 26/44] several changes: - missing escape sequence related methods in InputHandler implemented - created namespace for C1 control codes --- src/EscapeSequences.ts | 71 +++++++++++++++++++++++++++++++++++ src/InputHandler.ts | 85 ++++++++++++++++++++++++++++++++++++------ src/Types.ts | 21 ++++++++--- 3 files changed, 159 insertions(+), 18 deletions(-) diff --git a/src/EscapeSequences.ts b/src/EscapeSequences.ts index 11cbd84eb6..e35f01dd07 100644 --- a/src/EscapeSequences.ts +++ b/src/EscapeSequences.ts @@ -77,3 +77,74 @@ export namespace C0 { /** Delete (Caret = ^?) */ export const DEL = '\x7f'; } + +/** + * C1 control codes + * See = https://en.wikipedia.org/wiki/C0_and_C1_control_codes + */ +export namespace C1 { + /** padding character */ + export const PAD = '\x80'; + /** High Octet Preset */ + export const HOP = '\x81'; + /** Break Permitted Here */ + export const BPH = '\x82'; + /** No Break Here */ + export const NBH = '\x83'; + /** Index */ + export const IND = '\x84'; + /** Next Line */ + export const NEL = '\x85'; + /** Start of Selected Area */ + export const SSA = '\x86'; + /** End of Selected Area */ + export const ESA = '\x87'; + /** Horizontal Tabulation Set */ + export const HTS = '\x88'; + /** Horizontal Tabulation With Justification */ + export const HTJ = '\x89'; + /** Vertical Tabulation Set */ + export const VTS = '\x8a'; + /** Partial Line Down */ + export const PLD = '\x8b'; + /** Partial Line Up */ + export const PLU = '\x8c'; + /** Reverse Index */ + export const RI = '\x8d'; + /** Single-Shift 2 */ + export const SS2 = '\x8e'; + /** Single-Shift 3 */ + export const SS3 = '\x8f'; + /** Device Control String */ + export const DCS = '\x90'; + /** Private Use 1 */ + export const PU1 = '\x91'; + /** Private Use 2 */ + export const PU2 = '\x92'; + /** Set Transmit State */ + export const STS = '\x93'; + /** Destructive backspace, intended to eliminate ambiguity about meaning of BS. */ + export const CCH = '\x94'; + /** Message Waiting */ + export const MW = '\x95'; + /** Start of Protected Area */ + export const SPA = '\x96'; + /** End of Protected Area */ + export const EPA = '\x97'; + /** Start of String */ + export const SOS = '\x98'; + /** Single Graphic Character Introducer */ + export const SGCI = '\x99'; + /** Single Character Introducer */ + export const SCI = '\x9a'; + /** Control Sequence Introducer */ + export const CSI = '\x9b'; + /** String Terminator */ + export const ST = '\x9c'; + /** Operating System Command */ + export const OSC = '\x9d'; + /** Privacy Message */ + export const PM = '\x9e'; + /** Application Program Command */ + export const APC = '\x9f'; +} diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 8339a8beba..4779435aa6 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -5,7 +5,7 @@ */ import { CharData, IInputHandler } from './Types'; -import { C0 } from './EscapeSequences'; +import { C0, C1 } from './EscapeSequences'; import { CHARSETS, DEFAULT_CHARSET } from './Charsets'; import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from './Buffer'; import { FLAGS } from './renderer/Types'; @@ -108,6 +108,11 @@ export class InputHandler implements IInputHandler { // FIXME: What do to with missing? Old code just added those to print, but that's wrong // behavior for most control codes. + // some C1 control codes - should those be enabled by default? + this._parser.setExecuteHandler(C1.IND, () => this.index()); + this._parser.setExecuteHandler(C1.NEL, () => this.nextLine()); + this._parser.setExecuteHandler(C1.HTS, () => this.tabSet()); + /** * OSC handler */ @@ -154,18 +159,18 @@ export class InputHandler implements IInputHandler { */ this._parser.setEscHandler('', '7', () => this.saveCursor([])); this._parser.setEscHandler('', '8', () => this.restoreCursor([])); - this._parser.setEscHandler('', 'D', () => this._terminal.index()); // FIXME: move? + this._parser.setEscHandler('', 'D', () => this.index()); this._parser.setEscHandler('', 'E', () => this.nextLine()); - this._parser.setEscHandler('', 'H', () => this._terminal.tabSet()); // FIXME: move? - this._parser.setEscHandler('', 'M', () => this._terminal.reverseIndex()); // FIXME: move? + this._parser.setEscHandler('', 'H', () => this.tabSet()); + this._parser.setEscHandler('', 'M', () => this.reverseIndex()); this._parser.setEscHandler('', '=', () => this.keypadApplicationMode()); this._parser.setEscHandler('', '>', () => this.keypadNumericMode()); - this._parser.setEscHandler('', 'c', () => this._terminal.reset()); // FIXME: move? - this._parser.setEscHandler('', 'n', () => this._terminal.setgLevel(2)); // FIXME: move? - this._parser.setEscHandler('', 'o', () => this._terminal.setgLevel(3)); // FIXME: move? - this._parser.setEscHandler('', '|', () => this._terminal.setgLevel(3)); // FIXME: move? - this._parser.setEscHandler('', '}', () => this._terminal.setgLevel(2)); // FIXME: move? - this._parser.setEscHandler('', '~', () => this._terminal.setgLevel(1)); // FIXME: move? + this._parser.setEscHandler('', 'c', () => this.reset()); + this._parser.setEscHandler('', 'n', () => this.setgLevel(2)); + this._parser.setEscHandler('', 'o', () => this.setgLevel(3)); + this._parser.setEscHandler('', '|', () => this.setgLevel(3)); + this._parser.setEscHandler('', '}', () => this.setgLevel(2)); + this._parser.setEscHandler('', '~', () => this.setgLevel(1)); this._parser.setEscHandler('%', '@', () => this.selectDefaultCharset()); this._parser.setEscHandler('%', 'G', () => this.selectDefaultCharset()); for (let flag in CHARSETS) { @@ -175,7 +180,7 @@ export class InputHandler implements IInputHandler { this._parser.setEscHandler('+', flag, () => this.selectCharset('+' + flag)); this._parser.setEscHandler('-', flag, () => this.selectCharset('-' + flag)); this._parser.setEscHandler('.', flag, () => this.selectCharset('.' + flag)); - this._parser.setEscHandler('/', flag, () => this.selectCharset('/' + flag)); // FIXME + this._parser.setEscHandler('/', flag, () => this.selectCharset('/' + flag)); // FIXME: supported? } /** @@ -1727,12 +1732,13 @@ export class InputHandler implements IInputHandler { /** * ESC E + * C1.NEL * DEC mnemonic: NEL (https://vt100.net/docs/vt510-rm/NEL) * Moves cursor to first position on next line. */ public nextLine(): void { this._terminal.buffer.x = 0; - this._terminal.index(); + this.index(); } /** @@ -1793,4 +1799,59 @@ export class InputHandler implements IInputHandler { if (collectAndFlag[0] === '/') return; // FIXME: Is this supported? this._terminal.setgCharset(GLEVEL[collectAndFlag[0]], CHARSETS[collectAndFlag[1]] || DEFAULT_CHARSET); } + + /** + * ESC D + * C1.IND + * DEC mnemonic: IND (https://vt100.net/docs/vt510-rm/IND.html) + * Moves the cursor down one line in the same column. + */ + public index(): void { + this._terminal.index(); // FIXME: save to move the implementation from terminal? + } + + /** + * ESC H + * C1.HTS + * DEC mnemonic: HTS (https://vt100.net/docs/vt510-rm/HTS.html) + * Sets a horizontal tab stop at the column position indicated by + * the value of the active column when the terminal receives an HTS. + */ + public tabSet(): void { + this._terminal.tabSet(); // FIXME: save to move the implementation from terminal? + } + + /** + * ESC M + * C1.RI + * DEC mnemonic: HTS + * Moves the cursor up one line in the same column. If the cursor is at the top margin, + * the page scrolls down. + */ + public reverseIndex(): void { + this._terminal.reverseIndex(); // FIXME: save to move the implementation from terminal? + } + + /** + * ESC c + * DEC mnemonic: RIS (https://vt100.net/docs/vt510-rm/RIS.html) + * Reset to initial state. + */ + public reset(): void { + this._terminal.reset(); // FIXME: save to move the implementation from terminal? + } + + /** + * ESC n + * ESC o + * ESC | + * ESC } + * ESC ~ + * DEC mnemonic: LS (https://vt100.net/docs/vt510-rm/LS.html) + * When you use a locking shift, the character set remains in GL or GR until + * you use another locking shift. (partly supported) + */ + public setgLevel(level: number): void { + this._terminal.setgLevel(level); // FIXME: save to move the implementation from terminal? + } } diff --git a/src/Types.ts b/src/Types.ts index 59efe71f34..8cc5ae2c8f 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -163,12 +163,21 @@ export interface IInputHandler { /** ESC % G ESC % @ */ selectDefaultCharset(): void; /** ESC ( C - * ESC ) C - * ESC * C - * ESC + C - * ESC - C - * ESC . C - * ESC / C */ selectCharset(collectAndFlag: string): void; + ESC ) C + ESC * C + ESC + C + ESC - C + ESC . C + ESC / C */ selectCharset(collectAndFlag: string): void; + /** ESC D */ index(): void; + /** ESC H */ tabSet(): void; + /** ESC M */ reverseIndex(): void; + /** ESC c */ reset(): void; + /** ESC n + ESC o + ESC | + ESC } + ESC ~ */ setgLevel(level: number): void; } export interface ILinkMatcher { From 728eeebcc746bfcc0104005929650a3a38028943 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 1 May 2018 20:33:48 +0200 Subject: [PATCH 27/44] set member through ctor argument --- src/EscapeSequenceParser.ts | 4 +--- src/InputHandler.ts | 7 ++++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts index 7b336d060a..c595976ffc 100644 --- a/src/EscapeSequenceParser.ts +++ b/src/EscapeSequenceParser.ts @@ -200,7 +200,6 @@ class DcsDummy implements IDcsHandler { export class EscapeSequenceParser implements IEscapeSequenceParser { public initialState: number; public currentState: number; - readonly transitions: TransitionTable; // buffers over several parse calls // FIXME: make those protected (needs workaround in tests) @@ -227,10 +226,9 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { protected _dcsHandlerFb: IDcsHandler; protected _errorHandlerFb: (state: IParsingState) => IParsingState; - constructor(transitions: TransitionTable = VT500_TRANSITION_TABLE) { + constructor(readonly transitions: TransitionTable = VT500_TRANSITION_TABLE) { this.initialState = ParserState.GROUND; this.currentState = this.initialState; - this.transitions = transitions; this.osc = ''; this.params = [0]; this.collect = ''; diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 4779435aa6..3bb61bd7ad 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -25,11 +25,12 @@ const GLEVEL = {'(': 0, ')': 1, '*': 2, '+': 3, '-': 1, '.': 2}; * each function's header comment. */ export class InputHandler implements IInputHandler { - private _parser: EscapeSequenceParser; private _surrogateHigh: string; - constructor(private _terminal: any) { - this._parser = new EscapeSequenceParser; // FIXME: maybe as ctor argument + constructor( + private _terminal: any, + private _parser: EscapeSequenceParser = new EscapeSequenceParser) + { this._surrogateHigh = ''; /** From 0c200f2febb41393c2ec974e0fbfcd20a1a17d77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 1 May 2018 20:41:41 +0200 Subject: [PATCH 28/44] remove collect from args where not needed --- src/InputHandler.ts | 58 ++++++++++++++++++++++----------------------- src/Types.ts | 56 +++++++++++++++++++++---------------------- 2 files changed, 57 insertions(+), 57 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 3bb61bd7ad..7b601d28fb 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -86,7 +86,7 @@ export class InputHandler implements IInputHandler { this._parser.setCsiHandler('g', (params, collect) => this.tabClear(params)); this._parser.setCsiHandler('h', (params, collect) => this.setMode(params, collect)); this._parser.setCsiHandler('l', (params, collect) => this.resetMode(params, collect)); - this._parser.setCsiHandler('m', (params, collect) => this.charAttributes(params, collect)); + this._parser.setCsiHandler('m', (params, collect) => this.charAttributes(params)); this._parser.setCsiHandler('n', (params, collect) => this.deviceStatus(params, collect)); this._parser.setCsiHandler('p', (params, collect) => this.softReset(params, collect)); this._parser.setCsiHandler('q', (params, collect) => this.setCursorStyle(params, collect)); @@ -416,7 +416,7 @@ export class InputHandler implements IInputHandler { * CSI Ps @ * Insert Ps (Blank) Character(s) (default = 1) (ICH). */ - public insertChars(params: number[], collect?: string): void { + public insertChars(params: number[]): void { let param = params[0]; if (param < 1) param = 1; @@ -437,7 +437,7 @@ export class InputHandler implements IInputHandler { * CSI Ps A * Cursor Up Ps Times (default = 1) (CUU). */ - public cursorUp(params: number[], collect?: string): void { + public cursorUp(params: number[]): void { let param = params[0]; if (param < 1) { param = 1; @@ -452,7 +452,7 @@ export class InputHandler implements IInputHandler { * CSI Ps B * Cursor Down Ps Times (default = 1) (CUD). */ - public cursorDown(params: number[], collect?: string): void { + public cursorDown(params: number[]): void { let param = params[0]; if (param < 1) { param = 1; @@ -471,7 +471,7 @@ export class InputHandler implements IInputHandler { * CSI Ps C * Cursor Forward Ps Times (default = 1) (CUF). */ - public cursorForward(params: number[], collect?: string): void { + public cursorForward(params: number[]): void { let param = params[0]; if (param < 1) { param = 1; @@ -486,7 +486,7 @@ export class InputHandler implements IInputHandler { * CSI Ps D * Cursor Backward Ps Times (default = 1) (CUB). */ - public cursorBackward(params: number[], collect?: string): void { + public cursorBackward(params: number[]): void { let param = params[0]; if (param < 1) { param = 1; @@ -506,7 +506,7 @@ export class InputHandler implements IInputHandler { * Cursor Next Line Ps Times (default = 1) (CNL). * same as CSI Ps B ? */ - public cursorNextLine(params: number[], collect?: string): void { + public cursorNextLine(params: number[]): void { let param = params[0]; if (param < 1) { param = 1; @@ -524,7 +524,7 @@ export class InputHandler implements IInputHandler { * Cursor Preceding Line Ps Times (default = 1) (CNL). * reuse CSI Ps A ? */ - public cursorPrecedingLine(params: number[], collect?: string): void { + public cursorPrecedingLine(params: number[]): void { let param = params[0]; if (param < 1) { param = 1; @@ -541,7 +541,7 @@ export class InputHandler implements IInputHandler { * CSI Ps G * Cursor Character Absolute [column] (default = [row,1]) (CHA). */ - public cursorCharAbsolute(params: number[], collect?: string): void { + public cursorCharAbsolute(params: number[]): void { let param = params[0]; if (param < 1) { param = 1; @@ -553,7 +553,7 @@ export class InputHandler implements IInputHandler { * CSI Ps ; Ps H * Cursor Position [row;column] (default = [1,1]) (CUP). */ - public cursorPosition(params: number[], collect?: string): void { + public cursorPosition(params: number[]): void { let col: number; let row: number = params[0] - 1; @@ -583,7 +583,7 @@ export class InputHandler implements IInputHandler { * CSI Ps I * Cursor Forward Tabulation Ps tab stops (default = 1) (CHT). */ - public cursorForwardTab(params: number[], collect?: string): void { + public cursorForwardTab(params: number[]): void { let param = params[0] || 1; while (param--) { this._terminal.buffer.x = this._terminal.buffer.nextStop(); @@ -602,7 +602,7 @@ export class InputHandler implements IInputHandler { * Ps = 1 -> Selective Erase Above. * Ps = 2 -> Selective Erase All. */ - public eraseInDisplay(params: number[], collect?: string): void { + public eraseInDisplay(params: number[]): void { let j; switch (params[0]) { case 0: @@ -648,7 +648,7 @@ export class InputHandler implements IInputHandler { * Ps = 1 -> Selective Erase to Left. * Ps = 2 -> Selective Erase All. */ - public eraseInLine(params: number[], collect?: string): void { + public eraseInLine(params: number[]): void { switch (params[0]) { case 0: this._terminal.eraseRight(this._terminal.buffer.x, this._terminal.buffer.y); @@ -666,7 +666,7 @@ export class InputHandler implements IInputHandler { * CSI Ps L * Insert Ps Line(s) (default = 1) (IL). */ - public insertLines(params: number[], collect?: string): void { + public insertLines(params: number[]): void { let param: number = params[0]; if (param < 1) { param = 1; @@ -695,7 +695,7 @@ export class InputHandler implements IInputHandler { * CSI Ps M * Delete Ps Line(s) (default = 1) (DL). */ - public deleteLines(params: number[], collect?: string): void { + public deleteLines(params: number[]): void { let param = params[0]; if (param < 1) { param = 1; @@ -725,7 +725,7 @@ export class InputHandler implements IInputHandler { * CSI Ps P * Delete Ps Character(s) (default = 1) (DCH). */ - public deleteChars(params: number[], collect?: string): void { + public deleteChars(params: number[]): void { let param: number = params[0]; if (param < 1) { param = 1; @@ -747,7 +747,7 @@ export class InputHandler implements IInputHandler { /** * CSI Ps S Scroll up Ps lines (default = 1) (SU). */ - public scrollUp(params: number[], collect?: string): void { + public scrollUp(params: number[]): void { let param = params[0] || 1; // make buffer local for faster access @@ -786,7 +786,7 @@ export class InputHandler implements IInputHandler { * CSI Ps X * Erase Ps Character(s) (default = 1) (ECH). */ - public eraseChars(params: number[], collect?: string): void { + public eraseChars(params: number[]): void { let param = params[0]; if (param < 1) { param = 1; @@ -807,7 +807,7 @@ export class InputHandler implements IInputHandler { /** * CSI Ps Z Cursor Backward Tabulation Ps tab stops (default = 1) (CBT). */ - public cursorBackwardTab(params: number[], collect?: string): void { + public cursorBackwardTab(params: number[]): void { let param = params[0] || 1; // make buffer local for faster access @@ -822,7 +822,7 @@ export class InputHandler implements IInputHandler { * CSI Pm ` Character Position Absolute * [column] (default = [row,1]) (HPA). */ - public charPosAbsolute(params: number[], collect?: string): void { + public charPosAbsolute(params: number[]): void { let param = params[0]; if (param < 1) { param = 1; @@ -838,7 +838,7 @@ export class InputHandler implements IInputHandler { * [columns] (default = [row,col+1]) (HPR) * reuse CSI Ps C ? */ - public HPositionRelative(params: number[], collect?: string): void { + public HPositionRelative(params: number[]): void { let param = params[0]; if (param < 1) { param = 1; @@ -852,7 +852,7 @@ export class InputHandler implements IInputHandler { /** * CSI Ps b Repeat the preceding graphic character Ps times (REP). */ - public repeatPrecedingCharacter(params: number[], collect?: string): void { + public repeatPrecedingCharacter(params: number[]): void { let param = params[0] || 1; // make buffer local for faster access @@ -936,7 +936,7 @@ export class InputHandler implements IInputHandler { * CSI Pm d Vertical Position Absolute (VPA) * [row] (default = [1,column]) */ - public linePosAbsolute(params: number[], collect?: string): void { + public linePosAbsolute(params: number[]): void { let param = params[0]; if (param < 1) { param = 1; @@ -952,7 +952,7 @@ export class InputHandler implements IInputHandler { * [rows] (default = [row+1,column]) * reuse CSI Ps B ? */ - public VPositionRelative(params: number[], collect?: string): void { + public VPositionRelative(params: number[]): void { let param = params[0]; if (param < 1) { param = 1; @@ -972,7 +972,7 @@ export class InputHandler implements IInputHandler { * Horizontal and Vertical Position [row;column] (default = * [1,1]) (HVP). */ - public HVPosition(params: number[], collect?: string): void { + public HVPosition(params: number[]): void { if (params[0] < 1) params[0] = 1; if (params[1] < 1) params[1] = 1; @@ -995,7 +995,7 @@ export class InputHandler implements IInputHandler { * Ps = 2 -> Clear Stops on Line. * http://vt100.net/annarbor/aaa-ug/section6.html */ - public tabClear(params: number[], collect?: string): void { + public tabClear(params: number[]): void { let param = params[0]; if (param <= 0) { delete this._terminal.buffer.tabs[this._terminal.buffer.x]; @@ -1439,7 +1439,7 @@ export class InputHandler implements IInputHandler { * Ps = 4 8 ; 5 ; Ps -> Set background color to the second * Ps. */ - public charAttributes(params: number[], collect?: string): void { + public charAttributes(params: number[]): void { // Optimize a single SGR0. if (params.length === 1 && params[0] === 0) { this._terminal.curAttr = this._terminal.defAttr; @@ -1705,7 +1705,7 @@ export class InputHandler implements IInputHandler { * ESC 7 * Save cursor (ANSI.SYS). */ - public saveCursor(params: number[], collect?: string): void { + public saveCursor(params: number[]): void { this._terminal.buffer.savedX = this._terminal.buffer.x; this._terminal.buffer.savedY = this._terminal.buffer.y; } @@ -1716,7 +1716,7 @@ export class InputHandler implements IInputHandler { * ESC 8 * Restore cursor (ANSI.SYS). */ - public restoreCursor(params: number[], collect?: string): void { + public restoreCursor(params: number[]): void { this._terminal.buffer.x = this._terminal.buffer.savedX || 0; this._terminal.buffer.y = this._terminal.buffer.savedY || 0; } diff --git a/src/Types.ts b/src/Types.ts index 8cc5ae2c8f..8166ead42e 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -119,42 +119,42 @@ export interface IInputHandler { /** C0 SO */ shiftOut(): void; /** C0 SI */ shiftIn(): void; - /** CSI @ */ insertChars(params?: number[], collect?: string): void; - /** CSI A */ cursorUp(params?: number[], collect?: string): void; - /** CSI B */ cursorDown(params?: number[], collect?: string): void; - /** CSI C */ cursorForward(params?: number[], collect?: string): void; - /** CSI D */ cursorBackward(params?: number[], collect?: string): void; - /** CSI E */ cursorNextLine(params?: number[], collect?: string): void; - /** CSI F */ cursorPrecedingLine(params?: number[], collect?: string): void; - /** CSI G */ cursorCharAbsolute(params?: number[], collect?: string): void; - /** CSI H */ cursorPosition(params?: number[], collect?: string): void; - /** CSI I */ cursorForwardTab(params?: number[], collect?: string): void; - /** CSI J */ eraseInDisplay(params?: number[], collect?: string): void; - /** CSI K */ eraseInLine(params?: number[], collect?: string): void; - /** CSI L */ insertLines(params?: number[], collect?: string): void; - /** CSI M */ deleteLines(params?: number[], collect?: string): void; - /** CSI P */ deleteChars(params?: number[], collect?: string): void; - /** CSI S */ scrollUp(params?: number[], collect?: string): void; + /** CSI @ */ insertChars(params?: number[]): void; + /** CSI A */ cursorUp(params?: number[]): void; + /** CSI B */ cursorDown(params?: number[]): void; + /** CSI C */ cursorForward(params?: number[]): void; + /** CSI D */ cursorBackward(params?: number[]): void; + /** CSI E */ cursorNextLine(params?: number[]): void; + /** CSI F */ cursorPrecedingLine(params?: number[]): void; + /** CSI G */ cursorCharAbsolute(params?: number[]): void; + /** CSI H */ cursorPosition(params?: number[]): void; + /** CSI I */ cursorForwardTab(params?: number[]): void; + /** CSI J */ eraseInDisplay(params?: number[]): void; + /** CSI K */ eraseInLine(params?: number[]): void; + /** CSI L */ insertLines(params?: number[]): void; + /** CSI M */ deleteLines(params?: number[]): void; + /** CSI P */ deleteChars(params?: number[]): void; + /** CSI S */ scrollUp(params?: number[]): void; /** CSI T */ scrollDown(params?: number[], collect?: string): void; - /** CSI X */ eraseChars(params?: number[], collect?: string): void; - /** CSI Z */ cursorBackwardTab(params?: number[], collect?: string): void; - /** CSI ` */ charPosAbsolute(params?: number[], collect?: string): void; - /** CSI a */ HPositionRelative(params?: number[], collect?: string): void; - /** CSI b */ repeatPrecedingCharacter(params?: number[], collect?: string): void; + /** CSI X */ eraseChars(params?: number[]): void; + /** CSI Z */ cursorBackwardTab(params?: number[]): void; + /** CSI ` */ charPosAbsolute(params?: number[]): void; + /** CSI a */ HPositionRelative(params?: number[]): void; + /** CSI b */ repeatPrecedingCharacter(params?: number[]): void; /** CSI c */ sendDeviceAttributes(params?: number[], collect?: string): void; - /** CSI d */ linePosAbsolute(params?: number[], collect?: string): void; - /** CSI e */ VPositionRelative(params?: number[], collect?: string): void; - /** CSI f */ HVPosition(params?: number[], collect?: string): void; - /** CSI g */ tabClear(params?: number[], collect?: string): void; + /** CSI d */ linePosAbsolute(params?: number[]): void; + /** CSI e */ VPositionRelative(params?: number[]): void; + /** CSI f */ HVPosition(params?: number[]): void; + /** CSI g */ tabClear(params?: number[]): void; /** CSI h */ setMode(params?: number[], collect?: string): void; /** CSI l */ resetMode(params?: number[], collect?: string): void; - /** CSI m */ charAttributes(params?: number[], collect?: string): void; + /** CSI m */ charAttributes(params?: number[]): void; /** CSI n */ deviceStatus(params?: number[], collect?: string): void; /** CSI p */ softReset(params?: number[], collect?: string): void; /** CSI q */ setCursorStyle(params?: number[], collect?: string): void; /** CSI r */ setScrollRegion(params?: number[], collect?: string): void; - /** CSI s */ saveCursor(params?: number[], collect?: string): void; - /** CSI u */ restoreCursor(params?: number[], collect?: string): void; + /** CSI s */ saveCursor(params?: number[]): void; + /** CSI u */ restoreCursor(params?: number[]): void; /** OSC 0 OSC 2 */ setTitle(data: string): void; /** ESC E */ nextLine(): void; From 89cf45c514727bcc7cb55ea8bfe8e9c6b4ca1ab4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 1 May 2018 20:51:36 +0200 Subject: [PATCH 29/44] save func pointer in local var --- src/EscapeSequenceParser.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts index c595976ffc..9c35f25562 100644 --- a/src/EscapeSequenceParser.ts +++ b/src/EscapeSequenceParser.ts @@ -337,6 +337,7 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { const table: Uint8Array | number[] = this.transitions.table; let dcsHandler: IDcsHandler | null = this._activeDcsHandler; let ident: string = ''; // ugly workaround for ESC and DCS lookup keys + let callback: Function | null = null; // process input string const l = data.length; @@ -369,7 +370,8 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { this._printHandler(data, print, i); print = -1; } - if (this._executeHandlers[code]) this._executeHandlers[code](); + callback = this._executeHandlers[code]; + if (callback) callback(); else this._executeHandlerFb(code); break; case ParserAction.IGNORE: @@ -430,7 +432,8 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { } break; case ParserAction.CSI_DISPATCH: - if (this._csiHandlers[code]) this._csiHandlers[code](params, collect); + callback = this._csiHandlers[code]; + if (callback) callback(params, collect); else this._csiHandlerFb(collect, params, code); break; case ParserAction.PARAM: @@ -441,8 +444,8 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { collect += String.fromCharCode(code); break; case ParserAction.ESC_DISPATCH: - ident = collect + String.fromCharCode(code); - if (this._escHandlers[ident]) this._escHandlers[ident](collect, code); + callback = this._escHandlers[collect + String.fromCharCode(code)]; + if (callback) callback(collect, code); else this._escHandlerFb(collect, code); break; case ParserAction.CLEAR: @@ -457,8 +460,8 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { break; case ParserAction.DCS_HOOK: ident = collect + String.fromCharCode(code); - if (this._dcsHandlers[ident]) dcsHandler = this._dcsHandlers[ident]; - else dcsHandler = this._dcsHandlerFb; + dcsHandler = this._dcsHandlers[ident]; + if (!dcsHandler) dcsHandler = this._dcsHandlerFb; dcsHandler.hook(collect, params, code); break; case ParserAction.DCS_PUT: @@ -494,7 +497,8 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { } 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); + callback = this._oscHandlers[identifier]; + if (callback) callback(content); else this._oscHandlerFb(identifier, content); } } From 9a32852e613ab65dc564b193eabe27c5ba71b133 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 1 May 2018 21:07:13 +0200 Subject: [PATCH 30/44] single arg for DCS and ESC handler registration --- src/EscapeSequenceParser.ts | 20 ++++++++-------- src/InputHandler.ts | 46 ++++++++++++++++++------------------- src/Types.ts | 8 +++---- 3 files changed, 36 insertions(+), 38 deletions(-) diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts index 9c35f25562..a3ee6b846a 100644 --- a/src/EscapeSequenceParser.ts +++ b/src/EscapeSequenceParser.ts @@ -278,11 +278,11 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { this._csiHandlerFb = callback; } - setEscHandler(collect: string, flag: string, callback: (collect: string, flag: number) => void): void { - this._escHandlers[collect + flag] = callback; + setEscHandler(collectAndFlag: string, callback: () => void): void { + this._escHandlers[collectAndFlag] = callback; } - clearEscHandler(collect: string, flag: string): void { - if (this._escHandlers[collect + flag]) delete this._escHandlers[collect + flag]; + clearEscHandler(collectAndFlag: string): void { + if (this._escHandlers[collectAndFlag]) delete this._escHandlers[collectAndFlag]; } setEscHandlerFallback(callback: (...params: any[]) => void): void { this._escHandlerFb = callback; @@ -298,11 +298,11 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { this._oscHandlerFb = callback; } - setDcsHandler(collect: string, flag: string, handler: IDcsHandler): void { - this._dcsHandlers[collect + flag] = handler; + setDcsHandler(collectAndFlag: string, handler: IDcsHandler): void { + this._dcsHandlers[collectAndFlag] = handler; } - clearDcsHandler(collect: string, flag: string): void { - if (this._dcsHandlers[collect + flag]) delete this._dcsHandlers[collect + flag]; + clearDcsHandler(collectAndFlag: string): void { + if (this._dcsHandlers[collectAndFlag]) delete this._dcsHandlers[collectAndFlag]; } setDcsHandlerFallback(handler: IDcsHandler): void { this._dcsHandlerFb = handler; @@ -336,7 +336,6 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { 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 let callback: Function | null = null; // process input string @@ -459,8 +458,7 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { dcs = -1; break; case ParserAction.DCS_HOOK: - ident = collect + String.fromCharCode(code); - dcsHandler = this._dcsHandlers[ident]; + dcsHandler = this._dcsHandlers[collect + String.fromCharCode(code)]; if (!dcsHandler) dcsHandler = this._dcsHandlerFb; dcsHandler.hook(collect, params, code); break; diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 7b601d28fb..59b98cf8a7 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -158,30 +158,30 @@ export class InputHandler implements IInputHandler { /** * ESC handlers */ - this._parser.setEscHandler('', '7', () => this.saveCursor([])); - this._parser.setEscHandler('', '8', () => this.restoreCursor([])); - this._parser.setEscHandler('', 'D', () => this.index()); - this._parser.setEscHandler('', 'E', () => this.nextLine()); - this._parser.setEscHandler('', 'H', () => this.tabSet()); - this._parser.setEscHandler('', 'M', () => this.reverseIndex()); - this._parser.setEscHandler('', '=', () => this.keypadApplicationMode()); - this._parser.setEscHandler('', '>', () => this.keypadNumericMode()); - this._parser.setEscHandler('', 'c', () => this.reset()); - this._parser.setEscHandler('', 'n', () => this.setgLevel(2)); - this._parser.setEscHandler('', 'o', () => this.setgLevel(3)); - this._parser.setEscHandler('', '|', () => this.setgLevel(3)); - this._parser.setEscHandler('', '}', () => this.setgLevel(2)); - this._parser.setEscHandler('', '~', () => this.setgLevel(1)); - this._parser.setEscHandler('%', '@', () => this.selectDefaultCharset()); - this._parser.setEscHandler('%', 'G', () => this.selectDefaultCharset()); + this._parser.setEscHandler('7', () => this.saveCursor([])); + this._parser.setEscHandler('8', () => this.restoreCursor([])); + this._parser.setEscHandler('D', () => this.index()); + this._parser.setEscHandler('E', () => this.nextLine()); + this._parser.setEscHandler('H', () => this.tabSet()); + this._parser.setEscHandler('M', () => this.reverseIndex()); + this._parser.setEscHandler('=', () => this.keypadApplicationMode()); + this._parser.setEscHandler('>', () => this.keypadNumericMode()); + this._parser.setEscHandler('c', () => this.reset()); + this._parser.setEscHandler('n', () => this.setgLevel(2)); + this._parser.setEscHandler('o', () => this.setgLevel(3)); + this._parser.setEscHandler('|', () => this.setgLevel(3)); + this._parser.setEscHandler('}', () => this.setgLevel(2)); + this._parser.setEscHandler('~', () => this.setgLevel(1)); + this._parser.setEscHandler('%@', () => this.selectDefaultCharset()); + this._parser.setEscHandler('%G', () => this.selectDefaultCharset()); for (let flag in CHARSETS) { - this._parser.setEscHandler('(', flag, () => this.selectCharset('(' + flag)); - this._parser.setEscHandler(')', flag, () => this.selectCharset(')' + flag)); - this._parser.setEscHandler('*', flag, () => this.selectCharset('*' + flag)); - this._parser.setEscHandler('+', flag, () => this.selectCharset('+' + flag)); - this._parser.setEscHandler('-', flag, () => this.selectCharset('-' + flag)); - this._parser.setEscHandler('.', flag, () => this.selectCharset('.' + flag)); - this._parser.setEscHandler('/', flag, () => this.selectCharset('/' + flag)); // FIXME: supported? + this._parser.setEscHandler('(' + flag, () => this.selectCharset('(' + flag)); + this._parser.setEscHandler(')' + flag, () => this.selectCharset(')' + flag)); + this._parser.setEscHandler('*' + flag, () => this.selectCharset('*' + flag)); + this._parser.setEscHandler('+' + flag, () => this.selectCharset('+' + flag)); + this._parser.setEscHandler('-' + flag, () => this.selectCharset('-' + flag)); + this._parser.setEscHandler('.' + flag, () => this.selectCharset('.' + flag)); + this._parser.setEscHandler('/' + flag, () => this.selectCharset('/' + flag)); // FIXME: supported? } /** diff --git a/src/Types.ts b/src/Types.ts index 8166ead42e..bbe984759e 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -479,16 +479,16 @@ export interface IEscapeSequenceParser { clearCsiHandler(flag: string): void; setCsiHandlerFallback(callback: (...params: any[]) => void): void; - setEscHandler(collect: string, flag: string, callback: (collect: string, flag: number) => void): void; - clearEscHandler(collect: string, flag: string): void; + setEscHandler(collectAndFlag: string, callback: () => void): void; + clearEscHandler(collectAndFlag: string): void; setEscHandlerFallback(callback: (...params: any[]) => void): void; setOscHandler(ident: number, callback: (data: string) => void): 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; + setDcsHandler(collectAndFlag: string, handler: IDcsHandler): void; + clearDcsHandler(collectAndFlag: string): void; setDcsHandlerFallback(handler: IDcsHandler): void; setErrorHandler(callback: (state: IParsingState) => IParsingState): void; From d090cbe4b92712251f11136a5a3492c41f50a56b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 1 May 2018 21:54:15 +0200 Subject: [PATCH 31/44] some docs --- src/EscapeSequenceParser.ts | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts index a3ee6b846a..175bf44442 100644 --- a/src/EscapeSequenceParser.ts +++ b/src/EscapeSequenceParser.ts @@ -195,6 +195,11 @@ class DcsDummy implements IDcsHandler { * EscapeSequenceParser. * This class implements the ANSI/DEC compatible parser described by * Paul Williams (https://vt100.net/emu/dec_ansi_parser). + * To implement custom ANSI compliant escape sequences it is not needed to + * alter this parser, instead consider registering a custom handler. + * For non ANSI compliant sequences change the transition table with + * the optional `transitions` contructor argument and + * reimplement the `parse` method. * NOTE: The parameter element notation is currently not supported. */ export class EscapeSequenceParser implements IEscapeSequenceParser { @@ -207,7 +212,7 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { public params: number[]; public collect: string; - // callback slots + // handler lookup containers protected _printHandler: (data: string, start: number, end: number) => void; protected _executeHandlers: any; protected _csiHandlers: any; @@ -233,7 +238,7 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { this.params = [0]; this.collect = ''; - // set default fallback handlers + // set default fallback handlers and handler lookup containers this._printHandlerFb = (data, start, end): void => { }; this._executeHandlerFb = (...params: any[]): void => { }; this._csiHandlerFb = (...params: any[]): void => { }; @@ -315,20 +320,26 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { this._errorHandler = this._errorHandlerFb; } + /** + * Reset the parser. + */ reset(): void { this.currentState = this.initialState; this.osc = ''; this.params = [0]; this.collect = ''; + this._activeDcsHandler = null; } + /** + * Parse string `data`. + * @param data + */ 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; From 536b3a8defbdd2ba3910e9e854c8cb204d1b77b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 1 May 2018 22:17:26 +0200 Subject: [PATCH 32/44] fix member scope and tests --- src/EscapeSequenceParser.test.ts | 27 ++++++++++++++++++++++++++- src/EscapeSequenceParser.ts | 31 +++++++++++++++---------------- 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/src/EscapeSequenceParser.test.ts b/src/EscapeSequenceParser.test.ts index 1c5b6d6c62..c40a2b36fb 100644 --- a/src/EscapeSequenceParser.test.ts +++ b/src/EscapeSequenceParser.test.ts @@ -11,6 +11,30 @@ function r(a: number, b: number): string[] { return arr; } +class TestEscapeSequenceParser extends EscapeSequenceParser { + public get osc(): string { + return this._osc; + } + public set osc(value: string) { + this._osc = value; + } + public get params(): number[] { + return this._params; + } + public set params(value: number[]) { + this._params = value; + } + public get collect(): string { + return this._collect; + } + public set collect(value: string) { + this._collect = value; + } + public mockActiveDcsHandler(): void { + this._activeDcsHandler = this._dcsHandlerFb; + } +} + let testTerminal: any = { calls: [], clear: function (): void { @@ -75,7 +99,7 @@ let states: number[] = [ ]; let state: any; -let parser = new EscapeSequenceParser(); +let parser = new TestEscapeSequenceParser(); parser.setPrintHandler(testTerminal.print.bind(testTerminal)); parser.setCsiHandlerFallback((...params: any[]) => { testTerminal.actionCSI(params[0], params[1], String.fromCharCode(params[2])); @@ -872,6 +896,7 @@ describe('EscapeSequenceParser', function(): void { puts.concat(r(0x20, 0x7f)); for (let i = 0; i < puts.length; ++i) { parser.currentState = ParserState.DCS_PASSTHROUGH; + parser.mockActiveDcsHandler(); parser.parse(puts[i]); chai.expect(parser.currentState).equal(ParserState.DCS_PASSTHROUGH); testTerminal.compare([['dcs put', puts[i]]]); diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts index 175bf44442..cd21977e93 100644 --- a/src/EscapeSequenceParser.ts +++ b/src/EscapeSequenceParser.ts @@ -207,10 +207,9 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { public currentState: number; // buffers over several parse calls - // FIXME: make those protected (needs workaround in tests) - public osc: string; - public params: number[]; - public collect: string; + protected _osc: string; + protected _params: number[]; + protected _collect: string; // handler lookup containers protected _printHandler: (data: string, start: number, end: number) => void; @@ -234,9 +233,9 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { constructor(readonly transitions: TransitionTable = VT500_TRANSITION_TABLE) { this.initialState = ParserState.GROUND; this.currentState = this.initialState; - this.osc = ''; - this.params = [0]; - this.collect = ''; + this._osc = ''; + this._params = [0]; + this._collect = ''; // set default fallback handlers and handler lookup containers this._printHandlerFb = (data, start, end): void => { }; @@ -325,9 +324,9 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { */ reset(): void { this.currentState = this.initialState; - this.osc = ''; - this.params = [0]; - this.collect = ''; + this._osc = ''; + this._params = [0]; + this._collect = ''; this._activeDcsHandler = null; } @@ -342,9 +341,9 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { let currentState = this.currentState; let print = -1; let dcs = -1; - let osc = this.osc; - let collect = this.collect; - let params = this.params; + 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 callback: Function | null = null; @@ -529,9 +528,9 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { } // save non pushable buffers - this.osc = osc; - this.collect = collect; - this.params = params; + this._osc = osc; + this._collect = collect; + this._params = params; // save active dcs handler reference this._activeDcsHandler = dcsHandler; From 25ff77af0379135e0dcf0d25331391579b343e4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 1 May 2018 22:22:40 +0200 Subject: [PATCH 33/44] fix transition definitions --- src/EscapeSequenceParser.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts index cd21977e93..8104212986 100644 --- a/src/EscapeSequenceParser.ts +++ b/src/EscapeSequenceParser.ts @@ -1,8 +1,3 @@ -/** - * TODO: - * - docs - * - extend test cases - */ import { ParserState, ParserAction, IParsingState, IDcsHandler, IEscapeSequenceParser } from './Types'; // number range macro @@ -130,8 +125,8 @@ export const VT500_TRANSITION_TABLE = (function (): TransitionTable { 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(0x20, 0x40), ParserState.CSI_IGNORE, ParserAction.IGNORE, ParserState.CSI_IGNORE); + table.add(0x7f, ParserState.CSI_IGNORE, ParserAction.IGNORE, 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); From a1a4cac113289000f6cd3a2e0bcfa4f1140ec616 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Wed, 2 May 2018 00:22:35 +0200 Subject: [PATCH 34/44] dcs handler implementations --- src/InputHandler.ts | 91 ++++++++++++++++++++++++++++++++++++++++++++- src/Types.ts | 2 + 2 files changed, 92 insertions(+), 1 deletion(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 59b98cf8a7..6dfdd1bc48 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -4,7 +4,7 @@ * @license MIT */ -import { CharData, IInputHandler } from './Types'; +import { CharData, IInputHandler, IDcsHandler } from './Types'; import { C0, C1 } from './EscapeSequences'; import { CHARSETS, DEFAULT_CHARSET } from './Charsets'; import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from './Buffer'; @@ -17,6 +17,89 @@ import { EscapeSequenceParser } from './EscapeSequenceParser'; */ const GLEVEL = {'(': 0, ')': 1, '*': 2, '+': 3, '-': 1, '.': 2}; + +/** + * DCS subparser implementations + */ + + /** + * DCS + q Pt ST (xterm) + * Request Terminfo String + * not supported + */ +class RequestTerminfo implements IDcsHandler { + private _data: string; + constructor(private _terminal: any) { } + hook(collect: string, params: number[], flag: number): void { + this._data = ''; + } + put(data: string, start: number, end: number): void { + this._data += data.substring(start, end); + } + unhook(): void { + // invalid: DCS 0 + r Pt ST + this._terminal.send(C0.ESC + 'P0' + '+r' + this._data + C0.ESC + '\\'); + } +} + +/** + * DCS $ q Pt ST + * DECRQSS (https://vt100.net/docs/vt510-rm/DECRQSS.html) + * Request Status String (DECRQSS), VT420 and up. + * Response: DECRPSS (https://vt100.net/docs/vt510-rm/DECRPSS.html) + * FIXME: xterm and DEC flip P0 and P1 to indicate valid requests - which one to go with? + */ +class DECRQSS implements IDcsHandler { + private _data: string; + constructor(private _terminal: any) { } + hook(collect: string, params: number[], flag: number): void { + // reset data + this._data = ''; + } + put(data: string, start: number, end: number): void { + this._data += data.substring(start, end); + } + unhook(): void { + switch (this._data) { + // valid: DCS 1 $ r Pt ST (xterm) + case '"q': // DECSCA + return this._terminal.send(C0.ESC + 'P1' + '$r' + '0"q' + C0.ESC + '\\'); + case '"p': // DECSCL + return this._terminal.send(C0.ESC + 'P1' + '$r' + '61"p' + C0.ESC + '\\'); + case 'r': // DECSTBM + let pt = '' + (this._terminal.buffer.scrollTop + 1) + + ';' + (this._terminal.buffer.scrollBottom + 1) + 'r'; + return this._terminal.send(C0.ESC + 'P1' + '$r' + pt + C0.ESC + '\\'); + case 'm': // SGR + // FIXME: report real settings instead of 0m + return this._terminal.send(C0.ESC + 'P1' + '$r' + '0m' + C0.ESC + '\\'); + case ' q': // DECSCUSR + const STYLES = {'block': 2, 'underline': 4, 'bar': 6}; + let style = STYLES[this._terminal.getOption('cursorStyle')]; + style -= this._terminal.getOption('cursorBlink'); + return this._terminal.send(C0.ESC + 'P1' + '$r' + style + ' q' + C0.ESC + '\\'); + default: + // invalid: DCS 0 $ r Pt ST (xterm) + this._terminal.error('Unknown DCS $q %s', this._data); + this._terminal.send(C0.ESC + 'P0' + '$r' + this._data + C0.ESC + '\\'); + } + } +} + +/** + * DCS Ps; Ps| Pt ST + * DECUDK (https://vt100.net/docs/vt510-rm/DECUDK.html) + * not supported + */ + + /** + * DCS + p Pt ST (xterm) + * Set Terminfo Data + * not supported + */ + + + /** * The terminal's standard implementation of IInputHandler, this handles all * input from the Parser. @@ -191,6 +274,12 @@ export class InputHandler implements IInputHandler { this._terminal.error('Parsing error: ', state); return state; }); + + /** + * DCS handler + */ + this._parser.setDcsHandler('$q', new DECRQSS(this._terminal)); + this._parser.setDcsHandler('+q', new RequestTerminfo(this._terminal)); } public parse(data: string): void { diff --git a/src/Types.ts b/src/Types.ts index bbe984759e..5a46cbcac3 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -454,6 +454,8 @@ export interface IParsingState { * EscapeSequenceParser handles DCS commands via separate * subparsers that get hook/unhooked and can handle * arbitrary amount of print data. +* NOTE: EscapeSequenceParser might call `put` several times, +* therefore you have to collect `data` until unhook is called. */ export interface IDcsHandler { hook(collect: string, params: number[], flag: number): void; From 0f8d22547efe616bb137b06cab2ebc045fb5a0af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Wed, 2 May 2018 01:23:46 +0200 Subject: [PATCH 35/44] fix identation --- src/EscapeSequenceParser.test.ts | 1981 +++++++++++++++--------------- src/InputHandler.ts | 2 +- 2 files changed, 991 insertions(+), 992 deletions(-) diff --git a/src/EscapeSequenceParser.test.ts b/src/EscapeSequenceParser.test.ts index c40a2b36fb..1ad559bff8 100644 --- a/src/EscapeSequenceParser.test.ts +++ b/src/EscapeSequenceParser.test.ts @@ -3,1031 +3,1030 @@ import { EscapeSequenceParser } 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; + let c = b - a; + let arr = new Array(c); + while (c--) { + arr[c] = String.fromCharCode(--b); + } + return arr; } class TestEscapeSequenceParser extends EscapeSequenceParser { - public get osc(): string { - return this._osc; - } - public set osc(value: string) { - this._osc = value; - } - public get params(): number[] { - return this._params; - } - public set params(value: number[]) { - this._params = value; - } - public get collect(): string { - return this._collect; - } - public set collect(value: string) { - this._collect = value; - } - public mockActiveDcsHandler(): void { - this._activeDcsHandler = this._dcsHandlerFb; - } + public get osc(): string { + return this._osc; + } + public set osc(value: string) { + this._osc = value; + } + public get params(): number[] { + return this._params; + } + public set params(value: number[]) { + this._params = value; + } + public get collect(): string { + return this._collect; + } + public set collect(value: string) { + this._collect = value; + } + public mockActiveDcsHandler(): void { + this._activeDcsHandler = this._dcsHandlerFb; + } } let testTerminal: any = { - 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 (collect: string, params: number[], flag: string): void { - this.calls.push(['csi', collect, params, flag]); - }, - actionESC: function (collect: string, flag: string): void { - this.calls.push(['esc', collect, flag]); - }, - actionDCSHook: function (collect: string, params: number[], flag: string): void { - this.calls.push(['dcs hook', collect, 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']); - } + 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 (collect: string, params: number[], flag: string): void { + this.calls.push(['csi', collect, params, flag]); + }, + actionESC: function (collect: string, flag: string): void { + this.calls.push(['esc', collect, flag]); + }, + actionDCSHook: function (collect: string, params: number[], flag: string): void { + this.calls.push(['dcs hook', collect, 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']); + } }; class DcsTest implements IDcsHandler { - hook(collect: string, params: number[], flag: number): void { - testTerminal.actionDCSHook(collect, params, String.fromCharCode(flag)); - } - put(data: string, start: number, end: number): void { - testTerminal.actionDCSPrint(data, start, end); - } - unhook(): void { - testTerminal.actionDCSUnhook(); - } + hook(collect: string, params: number[], flag: number): void { + testTerminal.actionDCSHook(collect, params, String.fromCharCode(flag)); + } + put(data: string, start: number, end: number): void { + testTerminal.actionDCSPrint(data, start, end); + } + unhook(): void { + testTerminal.actionDCSUnhook(); + } } 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 + 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 TestEscapeSequenceParser(); parser.setPrintHandler(testTerminal.print.bind(testTerminal)); parser.setCsiHandlerFallback((...params: any[]) => { - testTerminal.actionCSI(params[0], params[1], String.fromCharCode(params[2])); + testTerminal.actionCSI(params[0], params[1], String.fromCharCode(params[2])); }); parser.setEscHandlerFallback((...params: any[]) => { - testTerminal.actionESC(params[0], String.fromCharCode(params[1])); + testTerminal.actionESC(params[0], String.fromCharCode(params[1])); }); parser.setExecuteHandlerFallback((...params: any[]) => { - testTerminal.actionExecute(String.fromCharCode(params[0])); + testTerminal.actionExecute(String.fromCharCode(params[0])); }); parser.setOscHandlerFallback((...params: any[]) => { - if (params[0] === -1) testTerminal.actionOSC(params[1]); // handle error condition silently - else testTerminal.actionOSC(params[0] + ';' + params[1]); + if (params[0] === -1) testTerminal.actionOSC(params[1]); // handle error condition silently + else testTerminal.actionOSC(params[0] + ';' + params[1]); }); parser.setDcsHandlerFallback(new DcsTest); -describe('EscapeSequenceParser', function(): void { - describe('Parser init and methods', function(): void { - it('inital states', function (): void { - chai.expect(parser.initialState).equal(ParserState.GROUND); - chai.expect(parser.currentState).equal(ParserState.GROUND); - chai.expect(parser.osc).equal(''); - chai.expect(parser.params).eql([0]); - chai.expect(parser.collect).equal(''); - }); - it('reset states', function (): void { - parser.currentState = 124; - parser.osc = '#'; - parser.params = [123]; - parser.collect = '#'; - - parser.reset(); - chai.expect(parser.currentState).equal(ParserState.GROUND); - chai.expect(parser.osc).equal(''); - chai.expect(parser.params).eql([0]); - chai.expect(parser.collect).equal(''); - }); +describe('EscapeSequenceParser', function (): void { + describe('Parser init and methods', function (): void { + it('inital states', function (): void { + chai.expect(parser.initialState).equal(ParserState.GROUND); + chai.expect(parser.currentState).equal(ParserState.GROUND); + chai.expect(parser.osc).equal(''); + chai.expect(parser.params).eql([0]); + chai.expect(parser.collect).equal(''); }); + it('reset states', function (): void { + parser.currentState = 124; + parser.osc = '#'; + parser.params = [123]; + parser.collect = '#'; - 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.collect = '#'; - parser.parse('\x1b'); - chai.expect(parser.currentState).equal(ParserState.ESCAPE); - chai.expect(parser.osc).equal(''); - chai.expect(parser.params).eql([0]); - chai.expect(parser.collect).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.collect).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.collect).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.collect = '#'; - parser.parse('['); - chai.expect(parser.currentState).equal(ParserState.CSI_ENTRY); - chai.expect(parser.osc).equal(''); - chai.expect(parser.params).eql([0]); - chai.expect(parser.collect).equal(''); - parser.reset(); - // C1 - for (state in states) { - parser.currentState = state; - parser.osc = '#'; - parser.params = [123]; - parser.collect = '#'; - 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.collect).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.collect).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.collect).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.collect).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.collect).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.collect).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.collect).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.collect).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.collect).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.collect).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.mockActiveDcsHandler(); - 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(); - }); + parser.reset(); + chai.expect(parser.currentState).equal(ParserState.GROUND); + chai.expect(parser.osc).equal(''); + chai.expect(parser.params).eql([0]); + chai.expect(parser.collect).equal(''); }); - - function test(s: string, value: any, noReset: any): void { - if (!noReset) { - parser.reset(); - testTerminal.clear(); + }); + 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.collect = '#'; + parser.parse('\x1b'); + chai.expect(parser.currentState).equal(ParserState.ESCAPE); + chai.expect(parser.osc).equal(''); + chai.expect(parser.params).eql([0]); + chai.expect(parser.collect).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.collect).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.collect).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.collect = '#'; + parser.parse('['); + chai.expect(parser.currentState).equal(ParserState.CSI_ENTRY); + chai.expect(parser.osc).equal(''); + chai.expect(parser.params).eql([0]); + chai.expect(parser.collect).equal(''); + parser.reset(); + // C1 + for (state in states) { + parser.currentState = state; + parser.osc = '#'; + parser.params = [123]; + parser.collect = '#'; + 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.collect).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.collect).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.collect).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.collect).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.collect).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(); } - parser.parse(s); - testTerminal.compare(value); + } + }); + 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.collect).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.collect).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.collect).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.collect).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.collect).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.mockActiveDcsHandler(); + 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('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('\x901;2;3+$a€öäü'); - chai.expect(parser.currentState).equal(ParserState.DCS_PASSTHROUGH); - testTerminal.compare([['dcs hook', '+$', [1, 2, 3], 'a'], ['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(); - }); - }); - // TODO: error conditions, higher order: set/clear of callbacks, custom sequences + 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('\x901;2;3+$a€öäü'); + chai.expect(parser.currentState).equal(ParserState.DCS_PASSTHROUGH); + testTerminal.compare([['dcs hook', '+$', [1, 2, 3], 'a'], ['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(); + }); + }); + // TODO: error conditions, higher order: set/clear of callbacks, custom sequences }); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 8415e11c6b..6e8fea56cf 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -192,7 +192,7 @@ export class InputHandler implements IInputHandler { // FIXME: What do to with missing? Old code just added those to print, but that's wrong // behavior for most control codes. - // some C1 control codes - should those be enabled by default? + // some C1 control codes - FIXME: should those be enabled by default? this._parser.setExecuteHandler(C1.IND, () => this.index()); this._parser.setExecuteHandler(C1.NEL, () => this.nextLine()); this._parser.setExecuteHandler(C1.HTS, () => this.tabSet()); From 6cefb4c634927471bfbb76641a33e7d623e31439 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sat, 5 May 2018 16:26:07 +0200 Subject: [PATCH 36/44] more test cases --- src/EscapeSequenceParser.test.ts | 158 ++++++++++++++++++++++++++++++- 1 file changed, 155 insertions(+), 3 deletions(-) diff --git a/src/EscapeSequenceParser.test.ts b/src/EscapeSequenceParser.test.ts index 1ad559bff8..c1b58087ea 100644 --- a/src/EscapeSequenceParser.test.ts +++ b/src/EscapeSequenceParser.test.ts @@ -1,5 +1,5 @@ -import { ParserState, IDcsHandler } from './Types'; -import { EscapeSequenceParser } from './EscapeSequenceParser'; +import { ParserState, IDcsHandler, IParsingState } from './Types'; +import { EscapeSequenceParser, TransitionTable, VT500_TRANSITION_TABLE } from './EscapeSequenceParser'; import * as chai from 'chai'; function r(a: number, b: number): string[] { @@ -119,6 +119,15 @@ parser.setDcsHandlerFallback(new DcsTest); describe('EscapeSequenceParser', function (): void { describe('Parser init and methods', function (): void { + it('constructor', function(): void { + let p: EscapeSequenceParser = new EscapeSequenceParser; + chai.expect(p.transitions).equal(VT500_TRANSITION_TABLE); + p = new EscapeSequenceParser(VT500_TRANSITION_TABLE); + chai.expect(p.transitions).equal(VT500_TRANSITION_TABLE); + let tansitions: TransitionTable = new TransitionTable(10); + p = new EscapeSequenceParser(tansitions); + chai.expect(p.transitions).equal(tansitions); + }); it('inital states', function (): void { chai.expect(parser.initialState).equal(ParserState.GROUND); chai.expect(parser.currentState).equal(ParserState.GROUND); @@ -1028,5 +1037,148 @@ describe('EscapeSequenceParser', function (): void { testTerminal.clear(); }); }); - // TODO: error conditions, higher order: set/clear of callbacks, custom sequences + + describe('set/clear handler', function(): void { + const INPUT = '\x1b[1;31mhello \x1b%Gwor\x1bEld!\x1b[0m\r\n$>\x1b]1;foo=bar\x1b\\'; + let parser2 = null; + let print = ''; + let esc = []; + let csi = []; + let exe = []; + let osc = []; + let dcs = []; + function clearAccu(): void { + print = ''; + esc = []; + csi = []; + exe = []; + osc = []; + dcs = []; + } + beforeEach(function(): void { + parser2 = new TestEscapeSequenceParser(); + clearAccu(); + }); + it('print handler', function(): void { + parser2.setPrintHandler(function(data: string, start: number, end: number): void { + print += data.substring(start, end); + }); + parser2.parse(INPUT); + chai.expect(print).equal('hello world!$>'); + parser2.clearPrintHandler(); + parser2.clearPrintHandler(); // should not throw + clearAccu(); + parser2.parse(INPUT); + chai.expect(print).equal(''); + }); + it('ESC handler', function(): void { + parser2.setEscHandler('%G', function (): void { + esc.push('%G'); + }); + parser2.setEscHandler('E', function (): void { + esc.push('E'); + }); + parser2.parse(INPUT); + chai.expect(esc).eql(['%G', 'E']); + parser2.clearEscHandler('%G'); + parser2.clearEscHandler('%G'); // should not throw + clearAccu(); + parser2.parse(INPUT); + chai.expect(esc).eql(['E']); + parser2.clearEscHandler('E'); + clearAccu(); + parser2.parse(INPUT); + chai.expect(esc).eql([]); + }); + it('CSI handler', function(): void { + parser2.setCsiHandler('m', function(params: number[], collect: string): void { + csi.push(['m', params, collect]); + }); + parser2.parse(INPUT); + chai.expect(csi).eql([['m', [1, 31], ''], ['m', [0], '']]); + parser2.clearCsiHandler('m'); + parser2.clearCsiHandler('m'); // should not throw + clearAccu(); + parser2.parse(INPUT); + chai.expect(csi).eql([]); + }); + it('EXECUTE handler', function(): void { + parser2.setExecuteHandler('\n', function(): void { + exe.push('\n'); + }); + parser2.setExecuteHandler('\r', function(): void { + exe.push('\r'); + }); + parser2.parse(INPUT); + chai.expect(exe).eql(['\r', '\n']); + parser2.clearExecuteHandler('\r'); + parser2.clearExecuteHandler('\r'); // should not throw + clearAccu(); + parser2.parse(INPUT); + chai.expect(exe).eql(['\n']); + }); + it('OSC handler', function(): void { + parser2.setOscHandler(1, function(data: string): void { + osc.push([1, data]); + }); + parser2.parse(INPUT); + chai.expect(osc).eql([[1, 'foo=bar']]); + parser2.clearOscHandler(1); + parser2.clearOscHandler(1); // should not throw + clearAccu(); + parser2.parse(INPUT); + chai.expect(osc).eql([]); + }); + it('DCS handler', function(): void { + parser2.setDcsHandler('+p', { + hook: function(collect: string, params: number[], flag: number): void { + dcs.push(['hook', collect, params, flag]); + }, + put: function(data: string, start: number, end: number): void { + dcs.push(['put', data.substring(start, end)]); + }, + unhook: function(): void { + dcs.push(['unhook']); + } + }); + parser2.parse('\x1bP1;2;3+pabc'); + parser2.parse(';de\x9c'); + chai.expect(dcs).eql([ + ['hook', '+', [1, 2, 3], 'p'.charCodeAt(0)], + ['put', 'abc'], ['put', ';de'], + ['unhook'] + ]); + parser2.clearDcsHandler('+p'); + parser2.clearDcsHandler('+p'); // should not throw + clearAccu(); + parser2.parse('\x1bP1;2;3+pabc'); + parser2.parse(';de\x9c'); + chai.expect(dcs).eql([]); + }); + it('ERROR handler', function(): void { + let errorState: IParsingState = null; + parser2.setErrorHandler(function(state: IParsingState): IParsingState { + errorState = state; + return state; + }); + parser2.parse('\x1b[1;2;€;3m'); // faulty escape sequence + chai.expect(errorState).eql({ + position: 6, + code: '€'.charCodeAt(0), + currentState: ParserState.CSI_PARAM, + print: -1, + dcs: -1, + osc: '', + collect: '', + params: [1, 2, 0], // extra zero here + abort: false + }); + parser2.clearErrorHandler(); + parser2.clearErrorHandler(); // should not throw + errorState = null; + parser2.parse('\x1b[1;2;a;3m'); + chai.expect(errorState).eql(null); + }); + }); + // TODO: error conditions }); From edb06c6b2a03b18fa61bab7d30beebb0f86d55ea Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 5 May 2018 07:54:25 -0700 Subject: [PATCH 37/44] Add copyright header --- src/EscapeSequenceParser.test.ts | 5 +++++ src/EscapeSequenceParser.ts | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/EscapeSequenceParser.test.ts b/src/EscapeSequenceParser.test.ts index c1b58087ea..d4f3d2f05b 100644 --- a/src/EscapeSequenceParser.test.ts +++ b/src/EscapeSequenceParser.test.ts @@ -1,3 +1,8 @@ +/** + * Copyright (c) 2018 The xterm.js authors. All rights reserved. + * @license MIT + */ + import { ParserState, IDcsHandler, IParsingState } from './Types'; import { EscapeSequenceParser, TransitionTable, VT500_TRANSITION_TABLE } from './EscapeSequenceParser'; import * as chai from 'chai'; diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts index 8104212986..c99d5c6f6c 100644 --- a/src/EscapeSequenceParser.ts +++ b/src/EscapeSequenceParser.ts @@ -1,3 +1,8 @@ +/** + * Copyright (c) 2018 The xterm.js authors. All rights reserved. + * @license MIT + */ + import { ParserState, ParserAction, IParsingState, IDcsHandler, IEscapeSequenceParser } from './Types'; // number range macro From 362d7698f7da62d444ca353242899235d4eceb23 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 5 May 2018 08:12:52 -0700 Subject: [PATCH 38/44] Some polish --- src/EscapeSequenceParser.test.ts | 4 ++-- src/EscapeSequenceParser.ts | 21 +++++++++------------ src/InputHandler.ts | 4 ++-- src/Types.ts | 8 ++++++++ tslint.json | 1 + 5 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/EscapeSequenceParser.test.ts b/src/EscapeSequenceParser.test.ts index d4f3d2f05b..4ef543a5fe 100644 --- a/src/EscapeSequenceParser.test.ts +++ b/src/EscapeSequenceParser.test.ts @@ -119,13 +119,13 @@ parser.setOscHandlerFallback((...params: any[]) => { if (params[0] === -1) testTerminal.actionOSC(params[1]); // handle error condition silently else testTerminal.actionOSC(params[0] + ';' + params[1]); }); -parser.setDcsHandlerFallback(new DcsTest); +parser.setDcsHandlerFallback(new DcsTest()); describe('EscapeSequenceParser', function (): void { describe('Parser init and methods', function (): void { it('constructor', function(): void { - let p: EscapeSequenceParser = new EscapeSequenceParser; + let p: EscapeSequenceParser = new EscapeSequenceParser(); chai.expect(p.transitions).equal(VT500_TRANSITION_TABLE); p = new EscapeSequenceParser(VT500_TRANSITION_TABLE); chai.expect(p.transitions).equal(VT500_TRANSITION_TABLE); diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts index c99d5c6f6c..ba59b3d52f 100644 --- a/src/EscapeSequenceParser.ts +++ b/src/EscapeSequenceParser.ts @@ -5,12 +5,16 @@ import { ParserState, ParserAction, IParsingState, IDcsHandler, IEscapeSequenceParser } from './Types'; -// number range macro -function r(a: number, b: number): number[] { - let c = b - a; +/** + * Returns an array fulled with numbers between the low and high parameters (inclusive). + * @param low The low number. + * @param high The high number. + */ +function r(low: number, high: number): number[] { + let c = high - low; let arr = new Array(c); while (c--) { - arr[c] = --b; + arr[c] = --high; } return arr; } @@ -243,7 +247,7 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { this._csiHandlerFb = (...params: any[]): void => { }; this._escHandlerFb = (...params: any[]): void => { }; this._oscHandlerFb = (...params: any[]): void => { }; - this._dcsHandlerFb = new DcsDummy; + this._dcsHandlerFb = new DcsDummy(); this._errorHandlerFb = (state: IParsingState): IParsingState => state; this._printHandler = this._printHandlerFb; this._executeHandlers = Object.create(null); @@ -319,9 +323,6 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { this._errorHandler = this._errorHandlerFb; } - /** - * Reset the parser. - */ reset(): void { this.currentState = this.initialState; this._osc = ''; @@ -330,10 +331,6 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { this._activeDcsHandler = null; } - /** - * Parse string `data`. - * @param data - */ parse(data: string): void { let code = 0; let transition = 0; diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 6e8fea56cf..b8f68fc16b 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -4,7 +4,7 @@ * @license MIT */ -import { CharData, IInputHandler, IDcsHandler } from './Types'; +import { CharData, IInputHandler, IDcsHandler, IEscapeSequenceParser } from './Types'; import { C0, C1 } from './EscapeSequences'; import { CHARSETS, DEFAULT_CHARSET } from './Charsets'; import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from './Buffer'; @@ -112,7 +112,7 @@ export class InputHandler implements IInputHandler { constructor( private _terminal: any, - private _parser: EscapeSequenceParser = new EscapeSequenceParser) + private _parser: IEscapeSequenceParser = new EscapeSequenceParser()) { this._surrogateHigh = ''; diff --git a/src/Types.ts b/src/Types.ts index 5a46cbcac3..60f923d7da 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -467,7 +467,15 @@ export interface IDcsHandler { * EscapeSequenceParser interface. */ export interface IEscapeSequenceParser { + /** + * Reset the parser to its initial state (handlers are kept). + */ reset(): void; + + /** + * Parse string `data`. + * @param data The data to parse. + */ parse(data: string): void; setPrintHandler(callback: (data: string, start: number, end: number) => void): void; diff --git a/tslint.json b/tslint.json index ac1b9c959a..67dc795584 100644 --- a/tslint.json +++ b/tslint.json @@ -31,6 +31,7 @@ "parameter" ], "eofline": true, + "new-parens": true, "no-duplicate-imports": true, "no-eval": true, "no-internal-module": true, From 108aa5ab317f6865285422037d61361c93bb65ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 6 May 2018 22:42:07 +0200 Subject: [PATCH 39/44] test Uint8Array and Array; fix Uint8Array type --- src/EscapeSequenceParser.test.ts | 1638 +++++++++++++++--------------- src/EscapeSequenceParser.ts | 4 +- 2 files changed, 848 insertions(+), 794 deletions(-) diff --git a/src/EscapeSequenceParser.test.ts b/src/EscapeSequenceParser.test.ts index 4ef543a5fe..fc9bbe7a8b 100644 --- a/src/EscapeSequenceParser.test.ts +++ b/src/EscapeSequenceParser.test.ts @@ -16,6 +16,7 @@ function r(a: number, b: number): string[] { return arr; } +// derived parser with access to internal states class TestEscapeSequenceParser extends EscapeSequenceParser { public get osc(): string { return this._osc; @@ -40,6 +41,7 @@ class TestEscapeSequenceParser extends EscapeSequenceParser { } } +// test object to collect parser actions and compare them with expected values let testTerminal: any = { calls: [], clear: function (): void { @@ -74,6 +76,7 @@ let testTerminal: any = { } }; +// dcs handler to map dcs actions into the test object `testTerminal` class DcsTest implements IDcsHandler { hook(collect: string, params: number[], flag: number): void { testTerminal.actionDCSHook(collect, params, String.fromCharCode(flag)); @@ -104,899 +107,950 @@ let states: number[] = [ ]; let state: any; -let parser = new TestEscapeSequenceParser(); -parser.setPrintHandler(testTerminal.print.bind(testTerminal)); -parser.setCsiHandlerFallback((...params: any[]) => { +// parser with Uint8Array based transition table +let parserUint = new TestEscapeSequenceParser(VT500_TRANSITION_TABLE); +parserUint.setPrintHandler(testTerminal.print.bind(testTerminal)); +parserUint.setCsiHandlerFallback((...params: any[]) => { testTerminal.actionCSI(params[0], params[1], String.fromCharCode(params[2])); }); -parser.setEscHandlerFallback((...params: any[]) => { +parserUint.setEscHandlerFallback((...params: any[]) => { testTerminal.actionESC(params[0], String.fromCharCode(params[1])); }); -parser.setExecuteHandlerFallback((...params: any[]) => { +parserUint.setExecuteHandlerFallback((...params: any[]) => { testTerminal.actionExecute(String.fromCharCode(params[0])); }); -parser.setOscHandlerFallback((...params: any[]) => { +parserUint.setOscHandlerFallback((...params: any[]) => { if (params[0] === -1) testTerminal.actionOSC(params[1]); // handle error condition silently else testTerminal.actionOSC(params[0] + ';' + params[1]); }); -parser.setDcsHandlerFallback(new DcsTest()); +parserUint.setDcsHandlerFallback(new DcsTest()); +// array based transition table +let VT500_TRANSITION_TABLE_ARRAY = new TransitionTable(VT500_TRANSITION_TABLE.table.length); +VT500_TRANSITION_TABLE_ARRAY.table = new Array(VT500_TRANSITION_TABLE.table.length); +for (let i = 0; i < VT500_TRANSITION_TABLE.table.length; ++i) { + VT500_TRANSITION_TABLE_ARRAY.table[i] = VT500_TRANSITION_TABLE.table[i]; +} -describe('EscapeSequenceParser', function (): void { - describe('Parser init and methods', function (): void { - it('constructor', function(): void { - let p: EscapeSequenceParser = new EscapeSequenceParser(); - chai.expect(p.transitions).equal(VT500_TRANSITION_TABLE); - p = new EscapeSequenceParser(VT500_TRANSITION_TABLE); - chai.expect(p.transitions).equal(VT500_TRANSITION_TABLE); - let tansitions: TransitionTable = new TransitionTable(10); - p = new EscapeSequenceParser(tansitions); - chai.expect(p.transitions).equal(tansitions); - }); - it('inital states', function (): void { - chai.expect(parser.initialState).equal(ParserState.GROUND); - chai.expect(parser.currentState).equal(ParserState.GROUND); - chai.expect(parser.osc).equal(''); - chai.expect(parser.params).eql([0]); - chai.expect(parser.collect).equal(''); - }); - it('reset states', function (): void { - parser.currentState = 124; - parser.osc = '#'; - parser.params = [123]; - parser.collect = '#'; +// parser with array based transition table +let parserArray = new TestEscapeSequenceParser(VT500_TRANSITION_TABLE_ARRAY); +parserArray.setPrintHandler(testTerminal.print.bind(testTerminal)); +parserArray.setCsiHandlerFallback((...params: any[]) => { + testTerminal.actionCSI(params[0], params[1], String.fromCharCode(params[2])); +}); +parserArray.setEscHandlerFallback((...params: any[]) => { + testTerminal.actionESC(params[0], String.fromCharCode(params[1])); +}); +parserArray.setExecuteHandlerFallback((...params: any[]) => { + testTerminal.actionExecute(String.fromCharCode(params[0])); +}); +parserArray.setOscHandlerFallback((...params: any[]) => { + if (params[0] === -1) testTerminal.actionOSC(params[1]); // handle error condition silently + else testTerminal.actionOSC(params[0] + ';' + params[1]); +}); +parserArray.setDcsHandlerFallback(new DcsTest()); - parser.reset(); - chai.expect(parser.currentState).equal(ParserState.GROUND); - chai.expect(parser.osc).equal(''); - chai.expect(parser.params).eql([0]); - chai.expect(parser.collect).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]); +interface IRun { + tableType: string; + parser: TestEscapeSequenceParser; +} + + +describe('EscapeSequenceParser', function (): void { + let parser: TestEscapeSequenceParser | null = null; + const runs: IRun[] = [ + { tableType: 'Uint8Array', parser: parserUint }, + { tableType: 'Array', parser: parserArray } + ]; + runs.forEach(function (run: IRun): void { + describe('Parser init and methods / ' + run.tableType, function (): void { + before(function(): void { + parser = run.parser; + }); + it('constructor', function (): void { + let p: EscapeSequenceParser = new EscapeSequenceParser(); + chai.expect(p.transitions).equal(VT500_TRANSITION_TABLE); + p = new EscapeSequenceParser(VT500_TRANSITION_TABLE); + chai.expect(p.transitions).equal(VT500_TRANSITION_TABLE); + let tansitions: TransitionTable = new TransitionTable(10); + p = new EscapeSequenceParser(tansitions); + chai.expect(p.transitions).equal(tansitions); + }); + it('inital states', function (): void { + chai.expect(parser.initialState).equal(ParserState.GROUND); chai.expect(parser.currentState).equal(ParserState.GROUND); - testTerminal.compare([['exe', exes[i]]]); + chai.expect(parser.osc).equal(''); + chai.expect(parser.params).eql([0]); + chai.expect(parser.collect).equal(''); + }); + it('reset states', function (): void { + parser.currentState = 124; + parser.osc = '#'; + parser.params = [123]; + parser.collect = '#'; + 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]]]); + chai.expect(parser.osc).equal(''); + chai.expect(parser.params).eql([0]); + chai.expect(parser.collect).equal(''); + }); + }); + }); + runs.forEach(function (run: IRun): void { + describe('state transitions and actions / ' + run.tableType, function (): void { + before(function(): void { + parser = run.parser; + }); + it('state GROUND execute action', function (): void { 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) { + let exes = r(0x00, 0x18); + exes.concat(['\x19']); + exes.concat(r(0x1c, 0x20)); for (let i = 0; i < exes.length; ++i) { - parser.currentState = state; + parser.currentState = ParserState.GROUND; parser.parse(exes[i]); chai.expect(parser.currentState).equal(ParserState.GROUND); - testTerminal.compare(((exceptions[state]) ? exceptions[state][exes[i]] : 0) || [['exe', exes[i]]]); + testTerminal.compare([['exe', exes[i]]]); parser.reset(); testTerminal.clear(); } - parser.parse('\x9c'); - chai.expect(parser.currentState).equal(ParserState.GROUND); - testTerminal.compare([]); + }); + it('state GROUND print action', function (): void { 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.collect = '#'; - parser.parse('\x1b'); - chai.expect(parser.currentState).equal(ParserState.ESCAPE); - chai.expect(parser.osc).equal(''); - chai.expect(parser.params).eql([0]); - chai.expect(parser.collect).equal(''); + 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(); - } - }); - 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) { + 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.collect = '#'; + parser.parse('\x1b'); + chai.expect(parser.currentState).equal(ParserState.ESCAPE); + chai.expect(parser.osc).equal(''); + chai.expect(parser.params).eql([0]); + chai.expect(parser.collect).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(exes[i]); + parser.parse('\x7f'); chai.expect(parser.currentState).equal(ParserState.ESCAPE); - testTerminal.compare([['exe', exes[i]]]); + testTerminal.compare([]); 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]]]); + }); + it('trans ESCAPE --> GROUND with ecs_dispatch action', function (): void { 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.collect).equal(collect[i]); + 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(); - } - }); - 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]]]); + 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.collect).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(); - } - }); - 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]); + parser.parse('\x7f'); chai.expect(parser.currentState).equal(ParserState.ESCAPE_INTERMEDIATE); - chai.expect(parser.collect).equal(collect[i]); + testTerminal.compare([]); 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]]]); + 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.collect).equal(collect[i]); + parser.reset(); + } + }); + it('trans ESCAPE_INTERMEDIATE --> GROUND with esc_dispatch action', function (): void { 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.collect = '#'; - parser.parse('['); - chai.expect(parser.currentState).equal(ParserState.CSI_ENTRY); - chai.expect(parser.osc).equal(''); - chai.expect(parser.params).eql([0]); - chai.expect(parser.collect).equal(''); - parser.reset(); - // C1 - for (state in states) { - parser.currentState = state; + 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.collect = '#'; - parser.parse('\x9b'); + parser.parse('['); chai.expect(parser.currentState).equal(ParserState.CSI_ENTRY); chai.expect(parser.osc).equal(''); chai.expect(parser.params).eql([0]); chai.expect(parser.collect).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]]]); + // C1 + for (state in states) { + parser.currentState = state; + parser.osc = '#'; + parser.params = [123]; + parser.collect = '#'; + 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.collect).equal(''); + parser.reset(); + } + }); + it('state CSI_ENTRY execute rules', function (): void { 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]]]); + 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(); - } - }); - 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.parse('\x7f'); + chai.expect(parser.currentState).equal(ParserState.CSI_ENTRY); + testTerminal.compare([]); 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) { + 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(collect[i]); + parser.parse('\x3b'); chai.expect(parser.currentState).equal(ParserState.CSI_PARAM); - chai.expect(parser.collect).equal(collect[i]); + chai.expect(parser.params).eql([0, 0]); 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]]]); + 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.collect).equal(collect[i]); + parser.reset(); + } + }); + it('state CSI_PARAM execute rules', function (): void { 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) { + 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(params[i]); + parser.parse('\x3b'); chai.expect(parser.currentState).equal(ParserState.CSI_PARAM); - chai.expect(parser.params).eql([params[i].charCodeAt(0) - 48]); + chai.expect(parser.params).eql([0, 0]); 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]]]); + }); + it('state CSI_PARAM ignore', function (): void { 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.collect).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.collect).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.parse('\x7f'); + chai.expect(parser.currentState).equal(ParserState.CSI_PARAM); + testTerminal.compare([]); 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.collect).equal(collect[i]); + }); + it('trans CSI_PARAM --> GROUND with csi_dispatch action', function (): void { 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]]]); + 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.collect).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.collect).equal(collect[i]); + parser.reset(); + } + }); + it('state CSI_INTERMEDIATE execute rules', function (): void { 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]); + 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(); - } - }); - 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]); + 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.collect).equal(collect[i]); + parser.reset(); + } + }); + it('state CSI_INTERMEDIATE ignore', function (): void { 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]]]); + 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('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]); + }); + 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); - testTerminal.compare([]); + 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(); - } - }); - 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([]); + 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(); - } - }); - 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']; + 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(initializers[i]); + parser.parse('\x1b' + 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); + // 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(); - } - }); - 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(''); + 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(); - } - }); - 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]); + // C0 + parser.parse('\x1b]'); 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); + // 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(); - } - }); - 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]); + 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(); - } - }); - 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]); + // 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(); - } - 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) { + 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(collect[i]); + parser.parse('\x3b'); chai.expect(parser.currentState).equal(ParserState.DCS_PARAM); - chai.expect(parser.collect).equal(collect[i]); + chai.expect(parser.params).eql([0, 0]); 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); + 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.collect).equal(collect[i]); + parser.reset(); + } + }); + it('state DCS_PARAM ignore rules', function (): void { 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]); + 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(); - } - 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) { + 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' + chars[i]); - chai.expect(parser.currentState).equal(ParserState.DCS_IGNORE); + parser.parse('\x3b'); + chai.expect(parser.currentState).equal(ParserState.DCS_PARAM); 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); + }); + it('trans DCS_ENTRY --> DCS_IGNORE', function (): void { 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]); + parser.currentState = ParserState.DCS_ENTRY; + parser.parse('\x3a'); 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.collect).equal(collect[i]); + }); + it('trans DCS_PARAM --> DCS_IGNORE', function (): void { 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.collect).equal(collect[i]); + 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(); - } - }); - 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.collect).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.collect).equal('\x20'); + 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(); - } - }); - 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]]]); + 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.collect).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.collect).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.collect).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.collect).equal('\x20'); + parser.reset(); + } + }); + it('trans DCS_ENTRY --> DCS_PASSTHROUGH with hook', function (): void { 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]]]); + 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(); - } - }); - 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]]]); + 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.mockActiveDcsHandler(); + 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(); - } - }); - 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.mockActiveDcsHandler(); - parser.parse(puts[i]); + parser.parse('\x7f'); chai.expect(parser.currentState).equal(ParserState.DCS_PASSTHROUGH); - testTerminal.compare([['dcs put', puts[i]]]); + testTerminal.compare([]); 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'] + runs.forEach(function (run: IRun): void { + let test: Function | null = null; + describe('escape sequence examples / ' + run.tableType, function (): void { + before(function(): void { + parser = run.parser; + test = function(s: string, value: any, noReset: any): void { + if (!noReset) { + parser.reset(); + testTerminal.clear(); + } + parser.parse(s); + testTerminal.compare(value); + }; + }); + 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('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); + }); + 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); + }); }); }); @@ -1043,7 +1097,7 @@ describe('EscapeSequenceParser', function (): void { }); }); - describe('set/clear handler', function(): void { + describe('set/clear handler', function (): void { const INPUT = '\x1b[1;31mhello \x1b%Gwor\x1bEld!\x1b[0m\r\n$>\x1b]1;foo=bar\x1b\\'; let parser2 = null; let print = ''; @@ -1060,12 +1114,12 @@ describe('EscapeSequenceParser', function (): void { osc = []; dcs = []; } - beforeEach(function(): void { + beforeEach(function (): void { parser2 = new TestEscapeSequenceParser(); clearAccu(); }); - it('print handler', function(): void { - parser2.setPrintHandler(function(data: string, start: number, end: number): void { + it('print handler', function (): void { + parser2.setPrintHandler(function (data: string, start: number, end: number): void { print += data.substring(start, end); }); parser2.parse(INPUT); @@ -1076,7 +1130,7 @@ describe('EscapeSequenceParser', function (): void { parser2.parse(INPUT); chai.expect(print).equal(''); }); - it('ESC handler', function(): void { + it('ESC handler', function (): void { parser2.setEscHandler('%G', function (): void { esc.push('%G'); }); @@ -1095,8 +1149,8 @@ describe('EscapeSequenceParser', function (): void { parser2.parse(INPUT); chai.expect(esc).eql([]); }); - it('CSI handler', function(): void { - parser2.setCsiHandler('m', function(params: number[], collect: string): void { + it('CSI handler', function (): void { + parser2.setCsiHandler('m', function (params: number[], collect: string): void { csi.push(['m', params, collect]); }); parser2.parse(INPUT); @@ -1107,11 +1161,11 @@ describe('EscapeSequenceParser', function (): void { parser2.parse(INPUT); chai.expect(csi).eql([]); }); - it('EXECUTE handler', function(): void { - parser2.setExecuteHandler('\n', function(): void { + it('EXECUTE handler', function (): void { + parser2.setExecuteHandler('\n', function (): void { exe.push('\n'); }); - parser2.setExecuteHandler('\r', function(): void { + parser2.setExecuteHandler('\r', function (): void { exe.push('\r'); }); parser2.parse(INPUT); @@ -1122,8 +1176,8 @@ describe('EscapeSequenceParser', function (): void { parser2.parse(INPUT); chai.expect(exe).eql(['\n']); }); - it('OSC handler', function(): void { - parser2.setOscHandler(1, function(data: string): void { + it('OSC handler', function (): void { + parser2.setOscHandler(1, function (data: string): void { osc.push([1, data]); }); parser2.parse(INPUT); @@ -1134,15 +1188,15 @@ describe('EscapeSequenceParser', function (): void { parser2.parse(INPUT); chai.expect(osc).eql([]); }); - it('DCS handler', function(): void { + it('DCS handler', function (): void { parser2.setDcsHandler('+p', { - hook: function(collect: string, params: number[], flag: number): void { + hook: function (collect: string, params: number[], flag: number): void { dcs.push(['hook', collect, params, flag]); }, - put: function(data: string, start: number, end: number): void { + put: function (data: string, start: number, end: number): void { dcs.push(['put', data.substring(start, end)]); }, - unhook: function(): void { + unhook: function (): void { dcs.push(['unhook']); } }); @@ -1160,9 +1214,9 @@ describe('EscapeSequenceParser', function (): void { parser2.parse(';de\x9c'); chai.expect(dcs).eql([]); }); - it('ERROR handler', function(): void { + it('ERROR handler', function (): void { let errorState: IParsingState = null; - parser2.setErrorHandler(function(state: IParsingState): IParsingState { + parser2.setErrorHandler(function (state: IParsingState): IParsingState { errorState = state; return state; }); diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts index ba59b3d52f..5aabc2518c 100644 --- a/src/EscapeSequenceParser.ts +++ b/src/EscapeSequenceParser.ts @@ -28,9 +28,9 @@ export class TransitionTable { public table: Uint8Array | number[]; constructor(length: number) { - this.table = (typeof Uint32Array === 'undefined') + this.table = (typeof Uint8Array === 'undefined') ? new Array(length) - : new Uint32Array(length); + : new Uint8Array(length); } /** From 551ccc0f2fc0222847720443436a53878e85d26c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 6 May 2018 23:02:54 +0200 Subject: [PATCH 40/44] explicit arguments for fallback handlers --- src/EscapeSequenceParser.test.ts | 36 ++++++++++++++++---------------- src/EscapeSequenceParser.ts | 24 ++++++++++----------- src/InputHandler.ts | 16 +++++++------- src/Types.ts | 8 +++---- 4 files changed, 42 insertions(+), 42 deletions(-) diff --git a/src/EscapeSequenceParser.test.ts b/src/EscapeSequenceParser.test.ts index fc9bbe7a8b..01e018ef18 100644 --- a/src/EscapeSequenceParser.test.ts +++ b/src/EscapeSequenceParser.test.ts @@ -110,18 +110,18 @@ let state: any; // parser with Uint8Array based transition table let parserUint = new TestEscapeSequenceParser(VT500_TRANSITION_TABLE); parserUint.setPrintHandler(testTerminal.print.bind(testTerminal)); -parserUint.setCsiHandlerFallback((...params: any[]) => { - testTerminal.actionCSI(params[0], params[1], String.fromCharCode(params[2])); +parserUint.setCsiHandlerFallback((collect: string, params: number[], flag: number) => { + testTerminal.actionCSI(collect, params, String.fromCharCode(flag)); }); -parserUint.setEscHandlerFallback((...params: any[]) => { - testTerminal.actionESC(params[0], String.fromCharCode(params[1])); +parserUint.setEscHandlerFallback((collect: string, flag: number) => { + testTerminal.actionESC(collect, String.fromCharCode(flag)); }); -parserUint.setExecuteHandlerFallback((...params: any[]) => { - testTerminal.actionExecute(String.fromCharCode(params[0])); +parserUint.setExecuteHandlerFallback((code: number) => { + testTerminal.actionExecute(String.fromCharCode(code)); }); -parserUint.setOscHandlerFallback((...params: any[]) => { - if (params[0] === -1) testTerminal.actionOSC(params[1]); // handle error condition silently - else testTerminal.actionOSC(params[0] + ';' + params[1]); +parserUint.setOscHandlerFallback((identifier: number, data: string) => { + if (identifier === -1) testTerminal.actionOSC(data); // handle error condition silently + else testTerminal.actionOSC('' + identifier + ';' + data); }); parserUint.setDcsHandlerFallback(new DcsTest()); @@ -135,18 +135,18 @@ for (let i = 0; i < VT500_TRANSITION_TABLE.table.length; ++i) { // parser with array based transition table let parserArray = new TestEscapeSequenceParser(VT500_TRANSITION_TABLE_ARRAY); parserArray.setPrintHandler(testTerminal.print.bind(testTerminal)); -parserArray.setCsiHandlerFallback((...params: any[]) => { - testTerminal.actionCSI(params[0], params[1], String.fromCharCode(params[2])); +parserArray.setCsiHandlerFallback((collect: string, params: number[], flag: number) => { + testTerminal.actionCSI(collect, params, String.fromCharCode(flag)); }); -parserArray.setEscHandlerFallback((...params: any[]) => { - testTerminal.actionESC(params[0], String.fromCharCode(params[1])); +parserArray.setEscHandlerFallback((collect: string, flag: number) => { + testTerminal.actionESC(collect, String.fromCharCode(flag)); }); -parserArray.setExecuteHandlerFallback((...params: any[]) => { - testTerminal.actionExecute(String.fromCharCode(params[0])); +parserArray.setExecuteHandlerFallback((code: number) => { + testTerminal.actionExecute(String.fromCharCode(code)); }); -parserArray.setOscHandlerFallback((...params: any[]) => { - if (params[0] === -1) testTerminal.actionOSC(params[1]); // handle error condition silently - else testTerminal.actionOSC(params[0] + ';' + params[1]); +parserArray.setOscHandlerFallback((identifier: number, data: string) => { + if (identifier === -1) testTerminal.actionOSC(data); // handle error condition silently + else testTerminal.actionOSC('' + identifier + ';' + data); }); parserArray.setDcsHandlerFallback(new DcsTest()); diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts index 5aabc2518c..207c32e9ac 100644 --- a/src/EscapeSequenceParser.ts +++ b/src/EscapeSequenceParser.ts @@ -227,10 +227,10 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { // fallback handlers protected _printHandlerFb: (data: string, start: number, end: number) => void; - protected _executeHandlerFb: (...params: any[]) => void; - protected _csiHandlerFb: (...params: any[]) => void; - protected _escHandlerFb: (...params: any[]) => void; - protected _oscHandlerFb: (...params: any[]) => void; + protected _executeHandlerFb: (code: number) => void; + protected _csiHandlerFb: (collect: string, params: number[], flag: number) => void; + protected _escHandlerFb: (collect: string, flag: number) => void; + protected _oscHandlerFb: (identifier: number, data: string) => void; protected _dcsHandlerFb: IDcsHandler; protected _errorHandlerFb: (state: IParsingState) => IParsingState; @@ -243,10 +243,10 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { // set default fallback handlers and handler lookup containers 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._executeHandlerFb = (code: number): void => { }; + this._csiHandlerFb = (collect: string, params: number[], flag: number): void => { }; + this._escHandlerFb = (collect: string, flag: number): void => { }; + this._oscHandlerFb = (identifier: number, data: string): void => { }; this._dcsHandlerFb = new DcsDummy(); this._errorHandlerFb = (state: IParsingState): IParsingState => state; this._printHandler = this._printHandlerFb; @@ -272,7 +272,7 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { clearExecuteHandler(flag: string): void { if (this._executeHandlers[flag.charCodeAt(0)]) delete this._executeHandlers[flag.charCodeAt(0)]; } - setExecuteHandlerFallback(callback: (...params: any[]) => void): void { + setExecuteHandlerFallback(callback: (code: number) => void): void { this._executeHandlerFb = callback; } @@ -282,7 +282,7 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { clearCsiHandler(flag: string): void { if (this._csiHandlers[flag.charCodeAt(0)]) delete this._csiHandlers[flag.charCodeAt(0)]; } - setCsiHandlerFallback(callback: (...params: any[]) => void): void { + setCsiHandlerFallback(callback: (collect: string, params: number[], flag: number) => void): void { this._csiHandlerFb = callback; } @@ -292,7 +292,7 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { clearEscHandler(collectAndFlag: string): void { if (this._escHandlers[collectAndFlag]) delete this._escHandlers[collectAndFlag]; } - setEscHandlerFallback(callback: (...params: any[]) => void): void { + setEscHandlerFallback(callback: (collect: string, flag: number) => void): void { this._escHandlerFb = callback; } @@ -302,7 +302,7 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { clearOscHandler(ident: number): void { if (this._oscHandlers[ident]) delete this._oscHandlers[ident]; } - setOscHandlerFallback(callback: (...params: any[]) => void): void { + setOscHandlerFallback(callback: (identifier: number, data: string) => void): void { this._oscHandlerFb = callback; } diff --git a/src/InputHandler.ts b/src/InputHandler.ts index b8f68fc16b..397ec9849c 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -119,17 +119,17 @@ export class InputHandler implements IInputHandler { /** * custom fallback handlers */ - this._parser.setCsiHandlerFallback((...params: any[]) => { - this._terminal.error('Unknown CSI code: ', params); + this._parser.setCsiHandlerFallback((collect: string, params: number[], flag: number) => { + this._terminal.error('Unknown CSI code: ', collect, params, String.fromCharCode(flag)); }); - this._parser.setEscHandlerFallback((...params: any[]) => { - this._terminal.error('Unknown ESC code: ', params); + this._parser.setEscHandlerFallback((collect: string, flag: number) => { + this._terminal.error('Unknown ESC code: ', collect, String.fromCharCode(flag)); }); - this._parser.setExecuteHandlerFallback((...params: any[]) => { - this._terminal.error('Unknown EXECUTE code: ', params); + this._parser.setExecuteHandlerFallback((code: number) => { + this._terminal.error('Unknown EXECUTE code: ', code); }); - this._parser.setOscHandlerFallback((...params: any[]) => { - this._terminal.error('Unknown OSC code: ', params); + this._parser.setOscHandlerFallback((identifier: number, data: string) => { + this._terminal.error('Unknown OSC code: ', identifier, data); }); /** diff --git a/src/Types.ts b/src/Types.ts index 60f923d7da..52812e3db8 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -483,19 +483,19 @@ export interface IEscapeSequenceParser { setExecuteHandler(flag: string, callback: () => void): void; clearExecuteHandler(flag: string): void; - setExecuteHandlerFallback(callback: (...params: any[]) => void): void; + setExecuteHandlerFallback(callback: (code: number) => void): void; setCsiHandler(flag: string, callback: (params: number[], collect: string) => void): void; clearCsiHandler(flag: string): void; - setCsiHandlerFallback(callback: (...params: any[]) => void): void; + setCsiHandlerFallback(callback: (collect: string, params: number[], flag: number) => void): void; setEscHandler(collectAndFlag: string, callback: () => void): void; clearEscHandler(collectAndFlag: string): void; - setEscHandlerFallback(callback: (...params: any[]) => void): void; + setEscHandlerFallback(callback: (collect: string, flag: number) => void): void; setOscHandler(ident: number, callback: (data: string) => void): void; clearOscHandler(ident: number): void; - setOscHandlerFallback(callback: (...params: any[]) => void): void; + setOscHandlerFallback(callback: (identifier: number, data: string) => void): void; setDcsHandler(collectAndFlag: string, handler: IDcsHandler): void; clearDcsHandler(collectAndFlag: string): void; From a37449a8cd73d5fbddc6e5a4f9610b44accb5bf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 6 May 2018 23:33:52 +0200 Subject: [PATCH 41/44] some docs cleanup --- src/EscapeSequenceParser.test.ts | 2 +- src/EscapeSequenceParser.ts | 8 ++++++-- src/InputHandler.ts | 24 ++++++++++++++---------- src/Types.ts | 2 +- 4 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/EscapeSequenceParser.test.ts b/src/EscapeSequenceParser.test.ts index 01e018ef18..2d85c4665b 100644 --- a/src/EscapeSequenceParser.test.ts +++ b/src/EscapeSequenceParser.test.ts @@ -1239,5 +1239,5 @@ describe('EscapeSequenceParser', function (): void { chai.expect(errorState).eql(null); }); }); - // TODO: error conditions + // TODO: error conditions and error recovery (not implemented yet in parser) }); diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts index 207c32e9ac..7e71e8b5fe 100644 --- a/src/EscapeSequenceParser.ts +++ b/src/EscapeSequenceParser.ts @@ -205,6 +205,7 @@ class DcsDummy implements IDcsHandler { * the optional `transitions` contructor argument and * reimplement the `parse` method. * NOTE: The parameter element notation is currently not supported. + * TODO: implement error recovery hook via error handler return values */ export class EscapeSequenceParser implements IEscapeSequenceParser { public initialState: number; @@ -433,7 +434,7 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { abort: false }); if (inject.abort) return; - // FIXME: inject return values + // TODO: inject return values error = false; } break; @@ -500,7 +501,10 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { 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 + // Note: NaN is not handled here + // either catch it with the fallback handler + // or with an explicit NaN OSC handler + let identifier = parseInt(osc.substring(0, idx)); let content = osc.substring(idx + 1); callback = this._oscHandlers[identifier]; if (callback) callback(content); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 397ec9849c..16571c668a 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -51,14 +51,18 @@ class RequestTerminfo implements IDcsHandler { */ class DECRQSS implements IDcsHandler { private _data: string; + constructor(private _terminal: any) { } + hook(collect: string, params: number[], flag: number): void { // reset data this._data = ''; } + put(data: string, start: number, end: number): void { this._data += data.substring(start, end); } + unhook(): void { switch (this._data) { // valid: DCS 1 $ r Pt ST (xterm) @@ -71,7 +75,7 @@ class DECRQSS implements IDcsHandler { ';' + (this._terminal.buffer.scrollBottom + 1) + 'r'; return this._terminal.send(C0.ESC + 'P1' + '$r' + pt + C0.ESC + '\\'); case 'm': // SGR - // FIXME: report real settings instead of 0m + // TODO: report real settings instead of 0m return this._terminal.send(C0.ESC + 'P1' + '$r' + '0m' + C0.ESC + '\\'); case ' q': // DECSCUSR const STYLES = {'block': 2, 'underline': 4, 'bar': 6}; @@ -189,8 +193,7 @@ export class InputHandler implements IInputHandler { this._parser.setExecuteHandler(C0.HT, () => this.tab()); this._parser.setExecuteHandler(C0.SO, () => this.shiftOut()); this._parser.setExecuteHandler(C0.SI, () => this.shiftIn()); - // FIXME: What do to with missing? Old code just added those to print, but that's wrong - // behavior for most control codes. + // FIXME: What do to with missing? Old code just added those to print. // some C1 control codes - FIXME: should those be enabled by default? this._parser.setExecuteHandler(C1.IND, () => this.index()); @@ -264,7 +267,7 @@ export class InputHandler implements IInputHandler { this._parser.setEscHandler('+' + flag, () => this.selectCharset('+' + flag)); this._parser.setEscHandler('-' + flag, () => this.selectCharset('-' + flag)); this._parser.setEscHandler('.' + flag, () => this.selectCharset('.' + flag)); - this._parser.setEscHandler('/' + flag, () => this.selectCharset('/' + flag)); // FIXME: supported? + this._parser.setEscHandler('/' + flag, () => this.selectCharset('/' + flag)); // TODO: supported? } /** @@ -1889,7 +1892,7 @@ export class InputHandler implements IInputHandler { */ public selectCharset(collectAndFlag: string): void { if (collectAndFlag.length !== 2) return this.selectDefaultCharset(); - if (collectAndFlag[0] === '/') return; // FIXME: Is this supported? + if (collectAndFlag[0] === '/') return; // TODO: Is this supported? this._terminal.setgCharset(GLEVEL[collectAndFlag[0]], CHARSETS[collectAndFlag[1]] || DEFAULT_CHARSET); } @@ -1900,7 +1903,7 @@ export class InputHandler implements IInputHandler { * Moves the cursor down one line in the same column. */ public index(): void { - this._terminal.index(); // FIXME: save to move the implementation from terminal? + this._terminal.index(); // TODO: save to move from terminal? } /** @@ -1911,7 +1914,7 @@ export class InputHandler implements IInputHandler { * the value of the active column when the terminal receives an HTS. */ public tabSet(): void { - this._terminal.tabSet(); // FIXME: save to move the implementation from terminal? + this._terminal.tabSet(); // TODO: save to move from terminal? } /** @@ -1922,7 +1925,7 @@ export class InputHandler implements IInputHandler { * the page scrolls down. */ public reverseIndex(): void { - this._terminal.reverseIndex(); // FIXME: save to move the implementation from terminal? + this._terminal.reverseIndex(); // TODO: save to move from terminal? } /** @@ -1931,7 +1934,8 @@ export class InputHandler implements IInputHandler { * Reset to initial state. */ public reset(): void { - this._terminal.reset(); // FIXME: save to move the implementation from terminal? + this._parser.reset(); + this._terminal.reset(); // TODO: save to move from terminal? } /** @@ -1945,6 +1949,6 @@ export class InputHandler implements IInputHandler { * you use another locking shift. (partly supported) */ public setgLevel(level: number): void { - this._terminal.setgLevel(level); // FIXME: save to move the implementation from terminal? + this._terminal.setgLevel(level); // TODO: save to move from terminal? } } diff --git a/src/Types.ts b/src/Types.ts index 52812e3db8..b1aced5cc5 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -422,7 +422,7 @@ export const enum ParserAction { /** * Internal state of EscapeSequenceParser. - * Used as argument to the error handler to allow + * Used as argument of the error handler to allow * introspection at runtime on parse errors. * Return it with altered values to recover from * faulty states (not yet supported). From 02893dcbb83f4034dd3bd34f5f705b6fdec1ba4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 7 May 2018 23:28:40 +0200 Subject: [PATCH 42/44] fix some docs --- src/EscapeSequenceParser.ts | 8 +++++--- src/InputHandler.ts | 4 ++-- src/Types.ts | 9 +++++++-- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts index 7e71e8b5fe..dea87f8d56 100644 --- a/src/EscapeSequenceParser.ts +++ b/src/EscapeSequenceParser.ts @@ -6,7 +6,7 @@ import { ParserState, ParserAction, IParsingState, IDcsHandler, IEscapeSequenceParser } from './Types'; /** - * Returns an array fulled with numbers between the low and high parameters (inclusive). + * Returns an array filled with numbers between the low and high parameters (right exclusive). * @param low The low number. * @param high The high number. */ @@ -34,7 +34,7 @@ export class TransitionTable { } /** - * Add a new transition to the transition table. + * Add a transition to the transition table. * @param code input character code * @param state current parser state * @param action parser action to be done @@ -45,7 +45,7 @@ export class TransitionTable { } /** - * Add transitions for multiple input characters codes. + * Add transitions for multiple input character codes. * @param codes input character code array * @param state current parser state * @param action parser action to be done @@ -497,6 +497,8 @@ export class EscapeSequenceParser implements IEscapeSequenceParser { break; case ParserAction.OSC_END: if (osc && code !== 0x18 && code !== 0x1a) { + // NOTE: OSC subparsing is not part of the original parser + // we do basic identifier parsing here to offer a jump table for OSC as well let idx = osc.indexOf(';'); if (idx === -1) { this._oscHandlerFb(-1, osc); // this is an error (malformed OSC) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 16571c668a..9dae509d53 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -115,7 +115,7 @@ export class InputHandler implements IInputHandler { private _surrogateHigh: string; constructor( - private _terminal: any, + private _terminal: any, // TODO: reestablish IInputHandlingTerminal here private _parser: IEscapeSequenceParser = new EscapeSequenceParser()) { this._surrogateHigh = ''; @@ -1819,7 +1819,7 @@ export class InputHandler implements IInputHandler { /** * OSC 0; ST (set icon name + window title) - * OSC 2; ST (set icon name) + * OSC 2; ST (set window title) * Proxy to set window title. Icon name is not supported. */ public setTitle(data: string): void { diff --git a/src/Types.ts b/src/Types.ts index b1aced5cc5..5075daa9bf 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -454,8 +454,13 @@ export interface IParsingState { * EscapeSequenceParser handles DCS commands via separate * subparsers that get hook/unhooked and can handle * arbitrary amount of print data. -* NOTE: EscapeSequenceParser might call `put` several times, -* therefore you have to collect `data` until unhook is called. +* On entering a DSC sequence `hook` is called by +* `EscapeSequenceParser`. Use it to initialize or reset +* states needed to handle the current DCS sequence. +* EscapeSequenceParser will call `put` several times if the +* parsed string got splitted, therefore you might have to collect +* `data` until `unhook` is called. `unhook` marks the end +* of the current DCS sequence. */ export interface IDcsHandler { hook(collect: string, params: number[], flag: number): void; From ceb1432f75a98211dab839fcb2f5fc4c176878e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 21 May 2018 18:33:59 +0200 Subject: [PATCH 43/44] remove deprecated FIXME --- src/InputHandler.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 9dae509d53..92604689f6 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -47,7 +47,6 @@ class RequestTerminfo implements IDcsHandler { * DECRQSS (https://vt100.net/docs/vt510-rm/DECRQSS.html) * Request Status String (DECRQSS), VT420 and up. * Response: DECRPSS (https://vt100.net/docs/vt510-rm/DECRPSS.html) - * FIXME: xterm and DEC flip P0 and P1 to indicate valid requests - which one to go with? */ class DECRQSS implements IDcsHandler { private _data: string; From e97882e5ad42538da648f61a7ba25cb7c26f892d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 21 May 2018 18:52:27 +0200 Subject: [PATCH 44/44] use string templates in DCS responses --- src/InputHandler.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 92604689f6..1e620e5f84 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -38,7 +38,7 @@ class RequestTerminfo implements IDcsHandler { } unhook(): void { // invalid: DCS 0 + r Pt ST - this._terminal.send(C0.ESC + 'P0' + '+r' + this._data + C0.ESC + '\\'); + this._terminal.send(`${C0.ESC}P0+r${this._data}${C0.ESC}\\`); } } @@ -66,25 +66,25 @@ class DECRQSS implements IDcsHandler { switch (this._data) { // valid: DCS 1 $ r Pt ST (xterm) case '"q': // DECSCA - return this._terminal.send(C0.ESC + 'P1' + '$r' + '0"q' + C0.ESC + '\\'); + return this._terminal.send(`${C0.ESC}P1$r0"q${C0.ESC}\\`); case '"p': // DECSCL - return this._terminal.send(C0.ESC + 'P1' + '$r' + '61"p' + C0.ESC + '\\'); + return this._terminal.send(`${C0.ESC}P1$r61"p${C0.ESC}\\`); case 'r': // DECSTBM let pt = '' + (this._terminal.buffer.scrollTop + 1) + ';' + (this._terminal.buffer.scrollBottom + 1) + 'r'; - return this._terminal.send(C0.ESC + 'P1' + '$r' + pt + C0.ESC + '\\'); + return this._terminal.send(`${C0.ESC}P1$r${pt}${C0.ESC}\\`); case 'm': // SGR // TODO: report real settings instead of 0m - return this._terminal.send(C0.ESC + 'P1' + '$r' + '0m' + C0.ESC + '\\'); + return this._terminal.send(`${C0.ESC}P1$r0m${C0.ESC}\\`); case ' q': // DECSCUSR const STYLES = {'block': 2, 'underline': 4, 'bar': 6}; let style = STYLES[this._terminal.getOption('cursorStyle')]; style -= this._terminal.getOption('cursorBlink'); - return this._terminal.send(C0.ESC + 'P1' + '$r' + style + ' q' + C0.ESC + '\\'); + return this._terminal.send(`${C0.ESC}P1$r${style} q${C0.ESC}\\`); default: // invalid: DCS 0 $ r Pt ST (xterm) this._terminal.error('Unknown DCS $q %s', this._data); - this._terminal.send(C0.ESC + 'P0' + '$r' + this._data + C0.ESC + '\\'); + this._terminal.send(`${C0.ESC}P0$r${this._data}${C0.ESC}\\`); } } }