From 06e89549a88f89fd95978346345f554398d05a11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 23 Nov 2018 23:30:37 +0100 Subject: [PATCH] typed array support for parser --- src/EscapeSequenceParser.test.ts | 294 +++++++++++++++++-------------- src/EscapeSequenceParser.ts | 24 +-- src/InputHandler.test.ts | 4 +- src/InputHandler.ts | 92 ++++++---- src/Types.ts | 8 +- 5 files changed, 236 insertions(+), 186 deletions(-) diff --git a/src/EscapeSequenceParser.test.ts b/src/EscapeSequenceParser.test.ts index e92f412574..3e310b083a 100644 --- a/src/EscapeSequenceParser.test.ts +++ b/src/EscapeSequenceParser.test.ts @@ -50,8 +50,12 @@ const testTerminal: any = { 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)]); + print: function (data: Uint16Array, start: number, end: number): void { + let s = ''; + for (let i = start; i < end; ++i) { + s += String.fromCharCode(data[i]); + } + this.calls.push(['print', s]); }, actionOSC: function (s: string): void { this.calls.push(['osc', s]); @@ -68,8 +72,12 @@ const testTerminal: any = { 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)]); + actionDCSPrint: function (data: Uint16Array, start: number, end: number): void { + let s = ''; + for (let i = start; i < end; ++i) { + s += String.fromCharCode(data[i]); + } + this.calls.push(['dcs put', s]); }, actionDCSUnhook: function (): void { this.calls.push(['dcs unhook']); @@ -81,7 +89,7 @@ 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 { + put(data: Uint16Array, start: number, end: number): void { testTerminal.actionDCSPrint(data, start, end); } unhook(): void { @@ -155,6 +163,14 @@ interface IRun { parser: TestEscapeSequenceParser; } +// translate string based parse calls into typed array based +function parse(parser: TestEscapeSequenceParser, data: string): void { + const container = new Uint16Array(data.length); + for (let i = 0; i < data.length; ++i) { + container[i] = data.charCodeAt(i); + } + parser.parse(container, data.length); +} describe('EscapeSequenceParser', function (): void { let parser: TestEscapeSequenceParser | null = null; @@ -205,12 +221,12 @@ describe('EscapeSequenceParser', function (): void { it('state GROUND execute action', function (): void { parser.reset(); testTerminal.clear(); - const exes = r(0x00, 0x18); - exes.concat(['\x19']); - exes.concat(r(0x1c, 0x20)); + let exes = r(0x00, 0x18); + exes = exes.concat(['\x19']); + exes = exes.concat(r(0x1c, 0x20)); for (let i = 0; i < exes.length; ++i) { parser.currentState = ParserState.GROUND; - parser.parse(exes[i]); + parse(parser, exes[i]); chai.expect(parser.currentState).equal(ParserState.GROUND); testTerminal.compare([['exe', exes[i]]]); parser.reset(); @@ -223,7 +239,7 @@ describe('EscapeSequenceParser', function (): void { const printables = r(0x20, 0x7f); // NOTE: DEL excluded for (let i = 0; i < printables.length; ++i) { parser.currentState = ParserState.GROUND; - parser.parse(printables[i]); + parse(parser, printables[i]); chai.expect(parser.currentState).equal(ParserState.GROUND); testTerminal.compare([['print', printables[i]]]); parser.reset(); @@ -245,13 +261,13 @@ describe('EscapeSequenceParser', function (): void { for (state in states) { for (let i = 0; i < exes.length; ++i) { parser.currentState = state; - parser.parse(exes[i]); + parse(parser, exes[i]); chai.expect(parser.currentState).equal(ParserState.GROUND); testTerminal.compare((state in exceptions ? exceptions[state][exes[i]] : 0) || [['exe', exes[i]]]); parser.reset(); testTerminal.clear(); } - parser.parse('\x9c'); + parse(parser, '\x9c'); chai.expect(parser.currentState).equal(ParserState.GROUND); testTerminal.compare([]); parser.reset(); @@ -265,7 +281,7 @@ describe('EscapeSequenceParser', function (): void { parser.osc = '#'; parser.params = [23]; parser.collect = '#'; - parser.parse('\x1b'); + parse(parser, '\x1b'); chai.expect(parser.currentState).equal(ParserState.ESCAPE); chai.expect(parser.osc).equal(''); chai.expect(parser.params).eql([0]); @@ -276,12 +292,12 @@ describe('EscapeSequenceParser', function (): void { it('state ESCAPE execute rules', function (): void { parser.reset(); testTerminal.clear(); - const exes = r(0x00, 0x18); - exes.concat(['\x19']); - exes.concat(r(0x1c, 0x20)); + let exes = r(0x00, 0x18); + exes = exes.concat(['\x19']); + exes = exes.concat(r(0x1c, 0x20)); for (let i = 0; i < exes.length; ++i) { parser.currentState = ParserState.ESCAPE; - parser.parse(exes[i]); + parse(parser, exes[i]); chai.expect(parser.currentState).equal(ParserState.ESCAPE); testTerminal.compare([['exe', exes[i]]]); parser.reset(); @@ -292,7 +308,7 @@ describe('EscapeSequenceParser', function (): void { parser.reset(); testTerminal.clear(); parser.currentState = ParserState.ESCAPE; - parser.parse('\x7f'); + parse(parser, '\x7f'); chai.expect(parser.currentState).equal(ParserState.ESCAPE); testTerminal.compare([]); parser.reset(); @@ -301,13 +317,13 @@ describe('EscapeSequenceParser', function (): void { it('trans ESCAPE --> GROUND with ecs_dispatch action', function (): void { parser.reset(); testTerminal.clear(); - const dispatches = r(0x30, 0x50); - dispatches.concat(r(0x51, 0x58)); - dispatches.concat(['\x59', '\x5a', '\x5c']); - dispatches.concat(r(0x60, 0x7f)); + let dispatches = r(0x30, 0x50); + dispatches = dispatches.concat(r(0x51, 0x58)); + dispatches = dispatches.concat(['\x59', '\x5a']); // excluded \x5c + dispatches = dispatches.concat(r(0x60, 0x7f)); for (let i = 0; i < dispatches.length; ++i) { parser.currentState = ParserState.ESCAPE; - parser.parse(dispatches[i]); + parse(parser, dispatches[i]); chai.expect(parser.currentState).equal(ParserState.GROUND); testTerminal.compare([['esc', '', dispatches[i]]]); parser.reset(); @@ -319,7 +335,7 @@ describe('EscapeSequenceParser', function (): void { const collect = r(0x20, 0x30); for (let i = 0; i < collect.length; ++i) { parser.currentState = ParserState.ESCAPE; - parser.parse(collect[i]); + parse(parser, collect[i]); chai.expect(parser.currentState).equal(ParserState.ESCAPE_INTERMEDIATE); chai.expect(parser.collect).equal(collect[i]); parser.reset(); @@ -328,12 +344,12 @@ describe('EscapeSequenceParser', function (): void { it('state ESCAPE_INTERMEDIATE execute rules', function (): void { parser.reset(); testTerminal.clear(); - const exes = r(0x00, 0x18); - exes.concat(['\x19']); - exes.concat(r(0x1c, 0x20)); + let exes = r(0x00, 0x18); + exes = exes.concat(['\x19']); + exes = exes.concat(r(0x1c, 0x20)); for (let i = 0; i < exes.length; ++i) { parser.currentState = ParserState.ESCAPE_INTERMEDIATE; - parser.parse(exes[i]); + parse(parser, exes[i]); chai.expect(parser.currentState).equal(ParserState.ESCAPE_INTERMEDIATE); testTerminal.compare([['exe', exes[i]]]); parser.reset(); @@ -344,7 +360,7 @@ describe('EscapeSequenceParser', function (): void { parser.reset(); testTerminal.clear(); parser.currentState = ParserState.ESCAPE_INTERMEDIATE; - parser.parse('\x7f'); + parse(parser, '\x7f'); chai.expect(parser.currentState).equal(ParserState.ESCAPE_INTERMEDIATE); testTerminal.compare([]); parser.reset(); @@ -355,7 +371,7 @@ describe('EscapeSequenceParser', function (): void { const collect = r(0x20, 0x30); for (let i = 0; i < collect.length; ++i) { parser.currentState = ParserState.ESCAPE_INTERMEDIATE; - parser.parse(collect[i]); + parse(parser, collect[i]); chai.expect(parser.currentState).equal(ParserState.ESCAPE_INTERMEDIATE); chai.expect(parser.collect).equal(collect[i]); parser.reset(); @@ -367,7 +383,7 @@ describe('EscapeSequenceParser', function (): void { const collect = r(0x30, 0x7f); for (let i = 0; i < collect.length; ++i) { parser.currentState = ParserState.ESCAPE_INTERMEDIATE; - parser.parse(collect[i]); + parse(parser, collect[i]); chai.expect(parser.currentState).equal(ParserState.GROUND); // '\x5c' --> ESC + \ (7bit ST) parser does not expose this as it already got handled testTerminal.compare((collect[i] === '\x5c') ? [] : [['esc', '', collect[i]]]); @@ -382,7 +398,7 @@ describe('EscapeSequenceParser', function (): void { parser.osc = '#'; parser.params = [123]; parser.collect = '#'; - parser.parse('['); + parse(parser, '['); chai.expect(parser.currentState).equal(ParserState.CSI_ENTRY); chai.expect(parser.osc).equal(''); chai.expect(parser.params).eql([0]); @@ -394,7 +410,7 @@ describe('EscapeSequenceParser', function (): void { parser.osc = '#'; parser.params = [123]; parser.collect = '#'; - parser.parse('\x9b'); + parse(parser, '\x9b'); chai.expect(parser.currentState).equal(ParserState.CSI_ENTRY); chai.expect(parser.osc).equal(''); chai.expect(parser.params).eql([0]); @@ -405,12 +421,12 @@ describe('EscapeSequenceParser', function (): void { it('state CSI_ENTRY execute rules', function (): void { parser.reset(); testTerminal.clear(); - const exes = r(0x00, 0x18); - exes.concat(['\x19']); - exes.concat(r(0x1c, 0x20)); + let exes = r(0x00, 0x18); + exes = exes.concat(['\x19']); + exes = exes.concat(r(0x1c, 0x20)); for (let i = 0; i < exes.length; ++i) { parser.currentState = ParserState.CSI_ENTRY; - parser.parse(exes[i]); + parse(parser, exes[i]); chai.expect(parser.currentState).equal(ParserState.CSI_ENTRY); testTerminal.compare([['exe', exes[i]]]); parser.reset(); @@ -421,7 +437,7 @@ describe('EscapeSequenceParser', function (): void { parser.reset(); testTerminal.clear(); parser.currentState = ParserState.CSI_ENTRY; - parser.parse('\x7f'); + parse(parser, '\x7f'); chai.expect(parser.currentState).equal(ParserState.CSI_ENTRY); testTerminal.compare([]); parser.reset(); @@ -432,7 +448,7 @@ describe('EscapeSequenceParser', function (): void { const dispatches = r(0x40, 0x7f); for (let i = 0; i < dispatches.length; ++i) { parser.currentState = ParserState.CSI_ENTRY; - parser.parse(dispatches[i]); + parse(parser, dispatches[i]); chai.expect(parser.currentState).equal(ParserState.GROUND); testTerminal.compare([['csi', '', [0], dispatches[i]]]); parser.reset(); @@ -445,19 +461,19 @@ describe('EscapeSequenceParser', function (): void { const collect = ['\x3c', '\x3d', '\x3e', '\x3f']; for (let i = 0; i < params.length; ++i) { parser.currentState = ParserState.CSI_ENTRY; - parser.parse(params[i]); + parse(parser, 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'); + parse(parser, '\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]); + parse(parser, collect[i]); chai.expect(parser.currentState).equal(ParserState.CSI_PARAM); chai.expect(parser.collect).equal(collect[i]); parser.reset(); @@ -466,12 +482,12 @@ describe('EscapeSequenceParser', function (): void { it('state CSI_PARAM execute rules', function (): void { parser.reset(); testTerminal.clear(); - const exes = r(0x00, 0x18); - exes.concat(['\x19']); - exes.concat(r(0x1c, 0x20)); + let exes = r(0x00, 0x18); + exes = exes.concat(['\x19']); + exes = exes.concat(r(0x1c, 0x20)); for (let i = 0; i < exes.length; ++i) { parser.currentState = ParserState.CSI_PARAM; - parser.parse(exes[i]); + parse(parser, exes[i]); chai.expect(parser.currentState).equal(ParserState.CSI_PARAM); testTerminal.compare([['exe', exes[i]]]); parser.reset(); @@ -483,13 +499,13 @@ describe('EscapeSequenceParser', function (): void { const 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]); + parse(parser, 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'); + parse(parser, '\x3b'); chai.expect(parser.currentState).equal(ParserState.CSI_PARAM); chai.expect(parser.params).eql([0, 0]); parser.reset(); @@ -498,7 +514,7 @@ describe('EscapeSequenceParser', function (): void { parser.reset(); testTerminal.clear(); parser.currentState = ParserState.CSI_PARAM; - parser.parse('\x7f'); + parse(parser, '\x7f'); chai.expect(parser.currentState).equal(ParserState.CSI_PARAM); testTerminal.compare([]); parser.reset(); @@ -510,7 +526,7 @@ describe('EscapeSequenceParser', function (): void { for (let i = 0; i < dispatches.length; ++i) { parser.currentState = ParserState.CSI_PARAM; parser.params = [0, 1]; - parser.parse(dispatches[i]); + parse(parser, dispatches[i]); chai.expect(parser.currentState).equal(ParserState.GROUND); testTerminal.compare([['csi', '', [0, 1], dispatches[i]]]); parser.reset(); @@ -522,7 +538,7 @@ describe('EscapeSequenceParser', function (): void { const collect = r(0x20, 0x30); for (let i = 0; i < collect.length; ++i) { parser.currentState = ParserState.CSI_ENTRY; - parser.parse(collect[i]); + parse(parser, collect[i]); chai.expect(parser.currentState).equal(ParserState.CSI_INTERMEDIATE); chai.expect(parser.collect).equal(collect[i]); parser.reset(); @@ -533,7 +549,7 @@ describe('EscapeSequenceParser', function (): void { const collect = r(0x20, 0x30); for (let i = 0; i < collect.length; ++i) { parser.currentState = ParserState.CSI_PARAM; - parser.parse(collect[i]); + parse(parser, collect[i]); chai.expect(parser.currentState).equal(ParserState.CSI_INTERMEDIATE); chai.expect(parser.collect).equal(collect[i]); parser.reset(); @@ -542,12 +558,12 @@ describe('EscapeSequenceParser', function (): void { it('state CSI_INTERMEDIATE execute rules', function (): void { parser.reset(); testTerminal.clear(); - const exes = r(0x00, 0x18); - exes.concat(['\x19']); - exes.concat(r(0x1c, 0x20)); + let exes = r(0x00, 0x18); + exes = exes.concat(['\x19']); + exes = exes.concat(r(0x1c, 0x20)); for (let i = 0; i < exes.length; ++i) { parser.currentState = ParserState.CSI_INTERMEDIATE; - parser.parse(exes[i]); + parse(parser, exes[i]); chai.expect(parser.currentState).equal(ParserState.CSI_INTERMEDIATE); testTerminal.compare([['exe', exes[i]]]); parser.reset(); @@ -559,7 +575,7 @@ describe('EscapeSequenceParser', function (): void { const collect = r(0x20, 0x30); for (let i = 0; i < collect.length; ++i) { parser.currentState = ParserState.CSI_INTERMEDIATE; - parser.parse(collect[i]); + parse(parser, collect[i]); chai.expect(parser.currentState).equal(ParserState.CSI_INTERMEDIATE); chai.expect(parser.collect).equal(collect[i]); parser.reset(); @@ -569,7 +585,7 @@ describe('EscapeSequenceParser', function (): void { parser.reset(); testTerminal.clear(); parser.currentState = ParserState.CSI_INTERMEDIATE; - parser.parse('\x7f'); + parse(parser, '\x7f'); chai.expect(parser.currentState).equal(ParserState.CSI_INTERMEDIATE); testTerminal.compare([]); parser.reset(); @@ -581,7 +597,7 @@ describe('EscapeSequenceParser', function (): void { for (let i = 0; i < dispatches.length; ++i) { parser.currentState = ParserState.CSI_INTERMEDIATE; parser.params = [0, 1]; - parser.parse(dispatches[i]); + parse(parser, dispatches[i]); chai.expect(parser.currentState).equal(ParserState.GROUND); testTerminal.compare([['csi', '', [0, 1], dispatches[i]]]); parser.reset(); @@ -591,7 +607,7 @@ describe('EscapeSequenceParser', function (): void { it('trans CSI_ENTRY --> CSI_IGNORE', function (): void { parser.reset(); parser.currentState = ParserState.CSI_ENTRY; - parser.parse('\x3a'); + parse(parser, '\x3a'); chai.expect(parser.currentState).equal(ParserState.CSI_IGNORE); parser.reset(); }); @@ -600,7 +616,7 @@ describe('EscapeSequenceParser', function (): void { const chars = ['\x3a', '\x3c', '\x3d', '\x3e', '\x3f']; for (let i = 0; i < chars.length; ++i) { parser.currentState = ParserState.CSI_PARAM; - parser.parse('\x3b' + chars[i]); + parse(parser, '\x3b' + chars[i]); chai.expect(parser.currentState).equal(ParserState.CSI_IGNORE); chai.expect(parser.params).eql([0, 0]); parser.reset(); @@ -611,7 +627,7 @@ describe('EscapeSequenceParser', function (): void { const chars = r(0x30, 0x40); for (let i = 0; i < chars.length; ++i) { parser.currentState = ParserState.CSI_INTERMEDIATE; - parser.parse(chars[i]); + parse(parser, chars[i]); chai.expect(parser.currentState).equal(ParserState.CSI_IGNORE); chai.expect(parser.params).eql([0]); parser.reset(); @@ -620,12 +636,12 @@ describe('EscapeSequenceParser', function (): void { it('state CSI_IGNORE execute rules', function (): void { parser.reset(); testTerminal.clear(); - const exes = r(0x00, 0x18); - exes.concat(['\x19']); - exes.concat(r(0x1c, 0x20)); + let exes = r(0x00, 0x18); + exes = exes.concat(['\x19']); + exes = exes.concat(r(0x1c, 0x20)); for (let i = 0; i < exes.length; ++i) { parser.currentState = ParserState.CSI_IGNORE; - parser.parse(exes[i]); + parse(parser, exes[i]); chai.expect(parser.currentState).equal(ParserState.CSI_IGNORE); testTerminal.compare([['exe', exes[i]]]); parser.reset(); @@ -635,11 +651,11 @@ describe('EscapeSequenceParser', function (): void { it('state CSI_IGNORE ignore', function (): void { parser.reset(); testTerminal.clear(); - const ignored = r(0x20, 0x40); - ignored.concat(['\x7f']); + let ignored = r(0x20, 0x40); + ignored = ignored.concat(['\x7f']); for (let i = 0; i < ignored.length; ++i) { parser.currentState = ParserState.CSI_IGNORE; - parser.parse(ignored[i]); + parse(parser, ignored[i]); chai.expect(parser.currentState).equal(ParserState.CSI_IGNORE); testTerminal.compare([]); parser.reset(); @@ -652,7 +668,7 @@ describe('EscapeSequenceParser', function (): void { for (let i = 0; i < dispatches.length; ++i) { parser.currentState = ParserState.CSI_IGNORE; parser.params = [0, 1]; - parser.parse(dispatches[i]); + parse(parser, dispatches[i]); chai.expect(parser.currentState).equal(ParserState.GROUND); testTerminal.compare([]); parser.reset(); @@ -664,7 +680,7 @@ describe('EscapeSequenceParser', function (): void { // C0 let initializers = ['\x58', '\x5e', '\x5f']; for (let i = 0; i < initializers.length; ++i) { - parser.parse('\x1b' + initializers[i]); + parse(parser, '\x1b' + initializers[i]); chai.expect(parser.currentState).equal(ParserState.SOS_PM_APC_STRING); parser.reset(); } @@ -673,7 +689,7 @@ describe('EscapeSequenceParser', function (): void { parser.currentState = state; initializers = ['\x98', '\x9e', '\x9f']; for (let i = 0; i < initializers.length; ++i) { - parser.parse(initializers[i]); + parse(parser, initializers[i]); chai.expect(parser.currentState).equal(ParserState.SOS_PM_APC_STRING); parser.reset(); } @@ -681,13 +697,13 @@ describe('EscapeSequenceParser', function (): void { }); it('state SOS_PM_APC_STRING ignore rules', function (): void { parser.reset(); - const ignored = r(0x00, 0x18); - ignored.concat(['\x19']); - ignored.concat(r(0x1c, 0x20)); - ignored.concat(r(0x20, 0x80)); + let ignored = r(0x00, 0x18); + ignored = ignored.concat(['\x19']); + ignored = ignored.concat(r(0x1c, 0x20)); + ignored = ignored.concat(r(0x20, 0x80)); for (let i = 0; i < ignored.length; ++i) { parser.currentState = ParserState.SOS_PM_APC_STRING; - parser.parse(ignored[i]); + parse(parser, ignored[i]); chai.expect(parser.currentState).equal(ParserState.SOS_PM_APC_STRING); parser.reset(); } @@ -695,13 +711,13 @@ describe('EscapeSequenceParser', function (): void { it('trans ANYWHERE/ESCAPE --> OSC_STRING', function (): void { parser.reset(); // C0 - parser.parse('\x1b]'); + parse(parser, '\x1b]'); chai.expect(parser.currentState).equal(ParserState.OSC_STRING); parser.reset(); // C1 for (state in states) { parser.currentState = state; - parser.parse('\x9d'); + parse(parser, '\x9d'); chai.expect(parser.currentState).equal(ParserState.OSC_STRING); parser.reset(); } @@ -714,7 +730,7 @@ describe('EscapeSequenceParser', function (): void { '\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]); + parse(parser, ignored[i]); chai.expect(parser.currentState).equal(ParserState.OSC_STRING); chai.expect(parser.osc).equal(''); parser.reset(); @@ -725,7 +741,7 @@ describe('EscapeSequenceParser', function (): void { const puts = r(0x20, 0x80); for (let i = 0; i < puts.length; ++i) { parser.currentState = ParserState.OSC_STRING; - parser.parse(puts[i]); + parse(parser, puts[i]); chai.expect(parser.currentState).equal(ParserState.OSC_STRING); chai.expect(parser.osc).equal(puts[i]); parser.reset(); @@ -734,13 +750,13 @@ describe('EscapeSequenceParser', function (): void { it('state DCS_ENTRY', function (): void { parser.reset(); // C0 - parser.parse('\x1bP'); + parse(parser, '\x1bP'); chai.expect(parser.currentState).equal(ParserState.DCS_ENTRY); parser.reset(); // C1 for (state in states) { parser.currentState = state; - parser.parse('\x90'); + parse(parser, '\x90'); chai.expect(parser.currentState).equal(ParserState.DCS_ENTRY); parser.reset(); } @@ -753,7 +769,7 @@ describe('EscapeSequenceParser', function (): void { '\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]); + parse(parser, ignored[i]); chai.expect(parser.currentState).equal(ParserState.DCS_ENTRY); parser.reset(); } @@ -764,19 +780,19 @@ describe('EscapeSequenceParser', function (): void { const collect = ['\x3c', '\x3d', '\x3e', '\x3f']; for (let i = 0; i < params.length; ++i) { parser.currentState = ParserState.DCS_ENTRY; - parser.parse(params[i]); + parse(parser, 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'); + parse(parser, '\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]); + parse(parser, collect[i]); chai.expect(parser.currentState).equal(ParserState.DCS_PARAM); chai.expect(parser.collect).equal(collect[i]); parser.reset(); @@ -790,7 +806,7 @@ describe('EscapeSequenceParser', function (): void { '\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]); + parse(parser, ignored[i]); chai.expect(parser.currentState).equal(ParserState.DCS_PARAM); parser.reset(); } @@ -800,13 +816,13 @@ describe('EscapeSequenceParser', function (): void { const 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]); + parse(parser, 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'); + parse(parser, '\x3b'); chai.expect(parser.currentState).equal(ParserState.DCS_PARAM); chai.expect(parser.params).eql([0, 0]); parser.reset(); @@ -814,7 +830,7 @@ describe('EscapeSequenceParser', function (): void { it('trans DCS_ENTRY --> DCS_IGNORE', function (): void { parser.reset(); parser.currentState = ParserState.DCS_ENTRY; - parser.parse('\x3a'); + parse(parser, '\x3a'); chai.expect(parser.currentState).equal(ParserState.DCS_IGNORE); parser.reset(); }); @@ -823,7 +839,7 @@ describe('EscapeSequenceParser', function (): void { const chars = ['\x3a', '\x3c', '\x3d', '\x3e', '\x3f']; for (let i = 0; i < chars.length; ++i) { parser.currentState = ParserState.DCS_PARAM; - parser.parse('\x3b' + chars[i]); + parse(parser, '\x3b' + chars[i]); chai.expect(parser.currentState).equal(ParserState.DCS_IGNORE); chai.expect(parser.params).eql([0, 0]); parser.reset(); @@ -834,21 +850,21 @@ describe('EscapeSequenceParser', function (): void { const chars = r(0x30, 0x40); for (let i = 0; i < chars.length; ++i) { parser.currentState = ParserState.DCS_INTERMEDIATE; - parser.parse(chars[i]); + parse(parser, chars[i]); chai.expect(parser.currentState).equal(ParserState.DCS_IGNORE); parser.reset(); } }); it('state DCS_IGNORE ignore rules', function (): void { parser.reset(); - const ignored = [ + 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)); + ignored = ignored.concat(r(0x20, 0x80)); for (let i = 0; i < ignored.length; ++i) { parser.currentState = ParserState.DCS_IGNORE; - parser.parse(ignored[i]); + parse(parser, ignored[i]); chai.expect(parser.currentState).equal(ParserState.DCS_IGNORE); parser.reset(); } @@ -858,7 +874,7 @@ describe('EscapeSequenceParser', function (): void { const collect = r(0x20, 0x30); for (let i = 0; i < collect.length; ++i) { parser.currentState = ParserState.DCS_ENTRY; - parser.parse(collect[i]); + parse(parser, collect[i]); chai.expect(parser.currentState).equal(ParserState.DCS_INTERMEDIATE); chai.expect(parser.collect).equal(collect[i]); parser.reset(); @@ -869,7 +885,7 @@ describe('EscapeSequenceParser', function (): void { const collect = r(0x20, 0x30); for (let i = 0; i < collect.length; ++i) { parser.currentState = ParserState.DCS_PARAM; - parser.parse(collect[i]); + parse(parser, collect[i]); chai.expect(parser.currentState).equal(ParserState.DCS_INTERMEDIATE); chai.expect(parser.collect).equal(collect[i]); parser.reset(); @@ -883,7 +899,7 @@ describe('EscapeSequenceParser', function (): void { '\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]); + parse(parser, ignored[i]); chai.expect(parser.currentState).equal(ParserState.DCS_INTERMEDIATE); parser.reset(); } @@ -893,7 +909,7 @@ describe('EscapeSequenceParser', function (): void { const collect = r(0x20, 0x30); for (let i = 0; i < collect.length; ++i) { parser.currentState = ParserState.DCS_INTERMEDIATE; - parser.parse(collect[i]); + parse(parser, collect[i]); chai.expect(parser.currentState).equal(ParserState.DCS_INTERMEDIATE); chai.expect(parser.collect).equal(collect[i]); parser.reset(); @@ -904,7 +920,7 @@ describe('EscapeSequenceParser', function (): void { const chars = r(0x30, 0x40); for (let i = 0; i < chars.length; ++i) { parser.currentState = ParserState.DCS_INTERMEDIATE; - parser.parse('\x20' + chars[i]); + parse(parser, '\x20' + chars[i]); chai.expect(parser.currentState).equal(ParserState.DCS_IGNORE); chai.expect(parser.collect).equal('\x20'); parser.reset(); @@ -916,7 +932,7 @@ describe('EscapeSequenceParser', function (): void { const collect = r(0x40, 0x7f); for (let i = 0; i < collect.length; ++i) { parser.currentState = ParserState.DCS_ENTRY; - parser.parse(collect[i]); + parse(parser, collect[i]); chai.expect(parser.currentState).equal(ParserState.DCS_PASSTHROUGH); testTerminal.compare([['dcs hook', '', [0], collect[i]]]); parser.reset(); @@ -929,7 +945,7 @@ describe('EscapeSequenceParser', function (): void { const collect = r(0x40, 0x7f); for (let i = 0; i < collect.length; ++i) { parser.currentState = ParserState.DCS_PARAM; - parser.parse(collect[i]); + parse(parser, collect[i]); chai.expect(parser.currentState).equal(ParserState.DCS_PASSTHROUGH); testTerminal.compare([['dcs hook', '', [0], collect[i]]]); parser.reset(); @@ -942,7 +958,7 @@ describe('EscapeSequenceParser', function (): void { const collect = r(0x40, 0x7f); for (let i = 0; i < collect.length; ++i) { parser.currentState = ParserState.DCS_INTERMEDIATE; - parser.parse(collect[i]); + parse(parser, collect[i]); chai.expect(parser.currentState).equal(ParserState.DCS_PASSTHROUGH); testTerminal.compare([['dcs hook', '', [0], collect[i]]]); parser.reset(); @@ -952,14 +968,14 @@ describe('EscapeSequenceParser', function (): void { it('state DCS_PASSTHROUGH put action', function (): void { parser.reset(); testTerminal.clear(); - const puts = r(0x00, 0x18); - puts.concat(['\x19']); - puts.concat(r(0x1c, 0x20)); - puts.concat(r(0x20, 0x7f)); + let puts = r(0x00, 0x18); + puts = puts.concat(['\x19']); + puts = puts.concat(r(0x1c, 0x20)); + puts = puts.concat(r(0x20, 0x7f)); for (let i = 0; i < puts.length; ++i) { parser.currentState = ParserState.DCS_PASSTHROUGH; parser.mockActiveDcsHandler(); - parser.parse(puts[i]); + parse(parser, puts[i]); chai.expect(parser.currentState).equal(ParserState.DCS_PASSTHROUGH); testTerminal.compare([['dcs put', puts[i]]]); parser.reset(); @@ -970,7 +986,7 @@ describe('EscapeSequenceParser', function (): void { parser.reset(); testTerminal.clear(); parser.currentState = ParserState.DCS_PASSTHROUGH; - parser.parse('\x7f'); + parse(parser, '\x7f'); chai.expect(parser.currentState).equal(ParserState.DCS_PASSTHROUGH); testTerminal.compare([]); parser.reset(); @@ -989,7 +1005,7 @@ describe('EscapeSequenceParser', function (): void { parser.reset(); testTerminal.clear(); } - parser.parse(s); + parse(parser, s); testTerminal.compare(value); }; }); @@ -1067,7 +1083,7 @@ describe('EscapeSequenceParser', function (): void { parser.reset(); testTerminal.clear(); parser.currentState = ParserState.CSI_IGNORE; - parser.parse('€öäü'); + parse(parser, '€öäü'); chai.expect(parser.currentState).equal(ParserState.CSI_IGNORE); testTerminal.compare([]); parser.reset(); @@ -1077,7 +1093,7 @@ describe('EscapeSequenceParser', function (): void { parser.reset(); testTerminal.clear(); parser.currentState = ParserState.DCS_IGNORE; - parser.parse('€öäü'); + parse(parser, '€öäü'); chai.expect(parser.currentState).equal(ParserState.DCS_IGNORE); testTerminal.compare([]); parser.reset(); @@ -1087,7 +1103,7 @@ describe('EscapeSequenceParser', function (): void { parser.reset(); testTerminal.clear(); parser.currentState = ParserState.DCS_PASSTHROUGH; - parser.parse('\x901;2;3+$a€öäü'); + parse(parser, '\x901;2;3+$a€öäü'); chai.expect(parser.currentState).equal(ParserState.DCS_PASSTHROUGH); testTerminal.compare([['dcs hook', '+$', [1, 2, 3], 'a'], ['dcs put', '€öäü']]); parser.reset(); @@ -1097,7 +1113,7 @@ describe('EscapeSequenceParser', function (): void { parser.reset(); testTerminal.clear(); parser.currentState = ParserState.GROUND; - parser.parse('\x9c'); + parse(parser, '\x9c'); chai.expect(parser.currentState).equal(ParserState.GROUND); testTerminal.compare([]); parser.reset(); @@ -1127,15 +1143,17 @@ describe('EscapeSequenceParser', function (): void { clearAccu(); }); it('print handler', function (): void { - parser2.setPrintHandler(function (data: string, start: number, end: number): void { - print += data.substring(start, end); + parser2.setPrintHandler(function (data: Uint16Array, start: number, end: number): void { + for (let i = start; i < end; ++i) { + print += String.fromCharCode(data[i]); + } }); - parser2.parse(INPUT); + parse(parser2, INPUT); chai.expect(print).equal('hello world!$>'); parser2.clearPrintHandler(); parser2.clearPrintHandler(); // should not throw clearAccu(); - parser2.parse(INPUT); + parse(parser2, INPUT); chai.expect(print).equal(''); }); it('ESC handler', function (): void { @@ -1145,28 +1163,28 @@ describe('EscapeSequenceParser', function (): void { parser2.setEscHandler('E', function (): void { esc.push('E'); }); - parser2.parse(INPUT); + parse(parser2, INPUT); chai.expect(esc).eql(['%G', 'E']); parser2.clearEscHandler('%G'); parser2.clearEscHandler('%G'); // should not throw clearAccu(); - parser2.parse(INPUT); + parse(parser2, INPUT); chai.expect(esc).eql(['E']); parser2.clearEscHandler('E'); clearAccu(); - parser2.parse(INPUT); + parse(parser2, 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); + parse(parser2, INPUT); chai.expect(csi).eql([['m', [1, 31], ''], ['m', [0], '']]); parser2.clearCsiHandler('m'); parser2.clearCsiHandler('m'); // should not throw clearAccu(); - parser2.parse(INPUT); + parse(parser2, INPUT); chai.expect(csi).eql([]); }); it('EXECUTE handler', function (): void { @@ -1176,24 +1194,24 @@ describe('EscapeSequenceParser', function (): void { parser2.setExecuteHandler('\r', function (): void { exe.push('\r'); }); - parser2.parse(INPUT); + parse(parser2, INPUT); chai.expect(exe).eql(['\r', '\n']); parser2.clearExecuteHandler('\r'); parser2.clearExecuteHandler('\r'); // should not throw clearAccu(); - parser2.parse(INPUT); + parse(parser2, 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); + parse(parser2, INPUT); chai.expect(osc).eql([[1, 'foo=bar']]); parser2.clearOscHandler(1); parser2.clearOscHandler(1); // should not throw clearAccu(); - parser2.parse(INPUT); + parse(parser2, INPUT); chai.expect(osc).eql([]); }); it('DCS handler', function (): void { @@ -1201,15 +1219,19 @@ describe('EscapeSequenceParser', function (): 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 { - dcs.push(['put', data.substring(start, end)]); + put: function (data: Uint16Array, start: number, end: number): void { + let s = ''; + for (let i = start; i < end; ++i) { + s += String.fromCharCode(data[i]); + } + dcs.push(['put', s]); }, unhook: function (): void { dcs.push(['unhook']); } }); - parser2.parse('\x1bP1;2;3+pabc'); - parser2.parse(';de\x9c'); + parse(parser2, '\x1bP1;2;3+pabc'); + parse(parser2, ';de\x9c'); chai.expect(dcs).eql([ ['hook', '+', [1, 2, 3], 'p'.charCodeAt(0)], ['put', 'abc'], ['put', ';de'], @@ -1218,8 +1240,8 @@ describe('EscapeSequenceParser', function (): void { parser2.clearDcsHandler('+p'); parser2.clearDcsHandler('+p'); // should not throw clearAccu(); - parser2.parse('\x1bP1;2;3+pabc'); - parser2.parse(';de\x9c'); + parse(parser2, '\x1bP1;2;3+pabc'); + parse(parser2, ';de\x9c'); chai.expect(dcs).eql([]); }); it('ERROR handler', function (): void { @@ -1228,7 +1250,7 @@ describe('EscapeSequenceParser', function (): void { errorState = state; return state; }); - parser2.parse('\x1b[1;2;€;3m'); // faulty escape sequence + parse(parser2, '\x1b[1;2;€;3m'); // faulty escape sequence chai.expect(errorState).eql({ position: 6, code: '€'.charCodeAt(0), @@ -1243,7 +1265,7 @@ describe('EscapeSequenceParser', function (): void { parser2.clearErrorHandler(); parser2.clearErrorHandler(); // should not throw errorState = null; - parser2.parse('\x1b[1;2;a;3m'); + parse(parser2, '\x1b[1;2;a;3m'); chai.expect(errorState).eql(null); }); }); diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts index b38c50f543..9d0da3ef04 100644 --- a/src/EscapeSequenceParser.ts +++ b/src/EscapeSequenceParser.ts @@ -125,6 +125,7 @@ export const VT500_TRANSITION_TABLE = (function (): TransitionTable { 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); + table.add(0x7f, ParserState.SOS_PM_APC_STRING, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING); // 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); @@ -192,7 +193,7 @@ export const VT500_TRANSITION_TABLE = (function (): TransitionTable { */ class DcsDummy implements IDcsHandler { hook(collect: string, params: number[], flag: number): void { } - put(data: string, start: number, end: number): void { } + put(data: Uint16Array, start: number, end: number): void { } unhook(): void { } } @@ -218,7 +219,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP protected _collect: string; // handler lookup containers - protected _printHandler: (data: string, start: number, end: number) => void; + protected _printHandler: (data: Uint16Array, start: number, end: number) => void; protected _executeHandlers: any; protected _csiHandlers: any; protected _escHandlers: any; @@ -228,7 +229,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP protected _errorHandler: (state: IParsingState) => IParsingState; // fallback handlers - protected _printHandlerFb: (data: string, start: number, end: number) => void; + protected _printHandlerFb: (data: Uint16Array, start: number, end: number) => void; protected _executeHandlerFb: (code: number) => void; protected _csiHandlerFb: (collect: string, params: number[], flag: number) => void; protected _escHandlerFb: (collect: string, flag: number) => void; @@ -284,7 +285,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP this._errorHandler = null; } - setPrintHandler(callback: (data: string, start: number, end: number) => void): void { + setPrintHandler(callback: (data: Uint16Array, start: number, end: number) => void): void { this._printHandler = callback; } clearPrintHandler(): void { @@ -356,7 +357,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP this._activeDcsHandler = null; } - parse(data: string): void { + parse(data: Uint16Array, length: number): void { let code = 0; let transition = 0; let error = false; @@ -371,15 +372,14 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP let callback: Function | null = null; // process input string - const l = data.length; - for (let i = 0; i < l; ++i) { - code = data.charCodeAt(i); + for (let i = 0; i < length; ++i) { + code = data[i]; // shortcut for most chars (print action) if (currentState === ParserState.GROUND && code > 0x1f && code < 0x80) { print = (~print) ? print : i; do i++; - while (i < l && data.charCodeAt(i) > 0x1f && data.charCodeAt(i) < 0x80); + while (i < length && data[i] > 0x1f && data[i] < 0x80); i--; continue; } @@ -517,7 +517,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP osc = ''; break; case ParserAction.OSC_PUT: - osc += data.charAt(i); + osc += String.fromCharCode(code); break; case ParserAction.OSC_END: if (osc && code !== 0x18 && code !== 0x1a) { @@ -549,9 +549,9 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP // push leftover pushable buffers to terminal if (currentState === ParserState.GROUND && ~print) { - this._printHandler(data, print, data.length); + this._printHandler(data, print, length); } else if (currentState === ParserState.DCS_PASSTHROUGH && ~dcs && dcsHandler) { - dcsHandler.put(data, dcs, data.length); + dcsHandler.put(data, dcs, length); } // save non pushable buffers diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index b2fea06a3c..c92d317d04 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -503,7 +503,9 @@ describe('InputHandler', () => { it('should not cause an infinite loop (regression test)', () => { const term = new Terminal(); const inputHandler = new InputHandler(term); - inputHandler.print(String.fromCharCode(0x200B), 0, 1); + const container = new Uint16Array(10); + container[0] = 0x200B; + inputHandler.print(container, 0, 1); }); }); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 7604b01f69..9f5486061f 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -29,18 +29,26 @@ const GLEVEL: {[key: string]: number} = {'(': 0, ')': 1, '*': 2, '+': 3, '-': 1, * Request Terminfo String * not supported */ -class RequestTerminfo implements IDcsHandler { - private _data: string; + class RequestTerminfo implements IDcsHandler { + private _data: Uint16Array = new Uint16Array(0); 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); + put(data: Uint16Array, start: number, end: number): void { + const tmp = new Uint16Array(this._data.length + end - start); + tmp.set(this._data); + tmp.set(data.subarray(start, end), this._data.length); + this._data = data; } unhook(): void { + let data = ''; + for (let i = 0; i < this._data.length; ++i) { + data += String.fromCharCode(this._data[i]); + } + this._data = new Uint16Array(0); // dont hold memory longer than needed // invalid: DCS 0 + r Pt ST - this._terminal.handler(`${C0.ESC}P0+r${this._data}${C0.ESC}\\`); + this._terminal.handler(`${C0.ESC}P0+r${data}${C0.ESC}\\`); + this._data = new Uint16Array(0); } } @@ -51,21 +59,27 @@ class RequestTerminfo implements IDcsHandler { * Response: DECRPSS (https://vt100.net/docs/vt510-rm/DECRPSS.html) */ class DECRQSS implements IDcsHandler { - private _data: string; + private _data: Uint16Array = new Uint16Array(0); 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); + put(data: Uint16Array, start: number, end: number): void { + const tmp = new Uint16Array(this._data.length + end - start); + tmp.set(this._data); + tmp.set(data.subarray(start, end), this._data.length); + this._data = data; } unhook(): void { - switch (this._data) { + let data = ''; + for (let i = 0; i < this._data.length; ++i) { + data += String.fromCharCode(this._data[i]); + } + this._data = new Uint16Array(0); // dont hold memory longer than needed + switch (data) { // valid: DCS 1 $ r Pt ST (xterm) case '"q': // DECSCA return this._terminal.handler(`${C0.ESC}P1$r0"q${C0.ESC}\\`); @@ -85,8 +99,8 @@ class DECRQSS implements IDcsHandler { return this._terminal.handler(`${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.handler(`${C0.ESC}P0$r${this._data}${C0.ESC}\\`); + this._terminal.error('Unknown DCS $q %s', data); + this._terminal.handler(`${C0.ESC}P0$r${data}${C0.ESC}\\`); } } } @@ -97,11 +111,11 @@ class DECRQSS implements IDcsHandler { * not supported */ - /** - * DCS + p Pt ST (xterm) - * Set Terminfo Data - * not supported - */ +/** + * DCS + p Pt ST (xterm) + * Set Terminfo Data + * not supported + */ @@ -114,6 +128,7 @@ class DECRQSS implements IDcsHandler { */ export class InputHandler extends Disposable implements IInputHandler { private _surrogateFirst: string; + private _parseBuffer: Uint16Array = new Uint16Array(4096); constructor( protected _terminal: IInputHandlingTerminal, @@ -316,7 +331,13 @@ export class InputHandler extends Disposable implements IInputHandler { this._surrogateFirst = ''; } - this._parser.parse(data); + if (this._parseBuffer.length < data.length) { + this._parseBuffer = new Uint16Array(data.length); + } + for (let i = 0; i < data.length; ++i) { + this._parseBuffer[i] = data.charCodeAt(i); + } + this._parser.parse(this._parseBuffer, data.length); buffer = this._terminal.buffer; if (buffer.x !== cursorStartX || buffer.y !== cursorStartY) { @@ -324,9 +345,9 @@ export class InputHandler extends Disposable implements IInputHandler { } } - public print(data: string, start: number, end: number): void { - let char: string; + public print(data: Uint16Array, start: number, end: number): void { let code: number; + let char: string; let chWidth: number; const buffer: IBuffer = this._terminal.buffer; const charset: ICharset = this._terminal.charset; @@ -338,30 +359,30 @@ export class InputHandler extends Disposable implements IInputHandler { let bufferRow = buffer.lines.get(buffer.y + buffer.ybase); this._terminal.updateRange(buffer.y); - for (let stringPosition = start; stringPosition < end; ++stringPosition) { - char = data.charAt(stringPosition); - code = data.charCodeAt(stringPosition); + for (let pos = start; pos < end; ++pos) { + code = data[pos]; + char = String.fromCharCode(code); // surrogate pair handling if (0xD800 <= code && code <= 0xDBFF) { - if (++stringPosition >= end) { + if (++pos >= end) { // end of input: // handle pairs as true UTF-16 and wait for the second part // since we expect the input comming from a stream there is // a small chance that the surrogate pair got split // therefore we dont process the first char here, instead // it gets added as first char to the next processed chunk - this._surrogateFirst = char; + this._surrogateFirst = String.fromCharCode(code); continue; } - const second = data.charCodeAt(stringPosition); + const second = data[pos]; // if the second part is in surrogate pair range create the high codepoint // otherwise fall back to UCS-2 behavior (handle codepoints independently) if (0xDC00 <= second && second <= 0xDFFF) { code = (code - 0xD800) * 0x400 + second - 0xDC00 + 0x10000; - char += data.charAt(stringPosition); + char += String.fromCharCode(second); } else { - stringPosition--; + pos--; } } @@ -370,9 +391,14 @@ export class InputHandler extends Disposable implements IInputHandler { chWidth = wcwidth(code); // get charset replacement character - if (charset) { - char = charset[char] || char; - code = char.charCodeAt(0); + // charset are only defined for ASCII, therefore we only + // search for an replacement char if code < 127 + if (code < 127 && charset) { + const ch = charset[char]; + if (ch) { + code = ch.charCodeAt(0); + char = ch; + } } if (screenReaderMode) { diff --git a/src/Types.ts b/src/Types.ts index 0f60e6f7db..b1c125dc86 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -111,7 +111,7 @@ export interface ICompositionHelper { */ export interface IInputHandler { parse(data: string): void; - print(data: string, start: number, end: number): void; + print(data: Uint16Array, start: number, end: number): void; /** C0 BEL */ bell(): void; /** C0 LF */ lineFeed(): void; @@ -463,7 +463,7 @@ export interface IParsingState { */ export interface IDcsHandler { hook(collect: string, params: number[], flag: number): void; - put(data: string, start: number, end: number): void; + put(data: Uint16Array, start: number, end: number): void; unhook(): void; } @@ -480,9 +480,9 @@ export interface IEscapeSequenceParser extends IDisposable { * Parse string `data`. * @param data The data to parse. */ - parse(data: string): void; + parse(data: Uint16Array, length: number): void; - setPrintHandler(callback: (data: string, start: number, end: number) => void): void; + setPrintHandler(callback: (data: Uint16Array, start: number, end: number) => void): void; clearPrintHandler(): void; setExecuteHandler(flag: string, callback: () => void): void;