From 383849b36f154f7f9511e09694422312bc222b08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 17 Jun 2019 20:49:12 +0200 Subject: [PATCH 01/25] Params type implementation --- src/common/parser/Params.test.ts | 182 +++++++++++++++++++++++ src/common/parser/Params.ts | 238 +++++++++++++++++++++++++++++++ 2 files changed, 420 insertions(+) create mode 100644 src/common/parser/Params.test.ts create mode 100644 src/common/parser/Params.ts diff --git a/src/common/parser/Params.test.ts b/src/common/parser/Params.test.ts new file mode 100644 index 00000000..564b0d56 --- /dev/null +++ b/src/common/parser/Params.test.ts @@ -0,0 +1,182 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ +import { assert } from 'chai'; +import { Params } from 'common/parser/Params'; + +/** `Params` parser shim */ +function parse(params: Params, s: string): void { + params.reset(); + params.addParam(0); + let isSub = false; + for (let i = 0; i < s.length; ++i) { + let code = s.charCodeAt(i); + do { + switch (code) { + case 0x3b: + params.addParam(0); + isSub = false; + break; + case 0x3a: + params.addSubParam(-1); + isSub = true; + break; + default: // 0x30 - 0x39 + if (isSub) params.addSubParamDigit(code - 48); + else params.addParamDigit(code - 48); + } + } while (++i < s.length && (code = s.charCodeAt(i)) > 0x2f && code < 0x3c); + } +} + + +describe('Params', () => { + it('should respect ctor args', () => { + const params = new Params(12, 23); + assert.equal(params.params.length, 12); + assert.equal(params.subParams.length, 23); + assert.deepEqual(params.toArray(), []); + }); + it('addParam', () => { + const params = new Params(); + params.addParam(1); + assert.equal(params.length, 1); + assert.deepEqual(Array.prototype.slice.call(params.params, 0, params.length), [1]); + assert.deepEqual(params.toArray(), [1]); + params.addParam(23); + assert.equal(params.length, 2); + assert.deepEqual(Array.prototype.slice.call(params.params, 0, params.length), [1, 23]); + assert.deepEqual(params.toArray(), [1, 23]); + assert.equal(params.subParamsLength, 0); + }); + it('addSubParam', () => { + const params = new Params(); + params.addParam(1); + params.addSubParam(2); + params.addSubParam(3); + assert.equal(params.length, 1); + assert.equal(params.subParamsLength, 2); + assert.deepEqual(params.toArray(), [1, [2, 3]]); + params.addParam(12345); + params.addSubParam(-1); + assert.equal(params.length, 2); + assert.equal(params.subParamsLength, 3); + assert.deepEqual(params.toArray(), [1, [2, 3], 12345, [-1]]); + }); + it('should not add sub params without previous param', () => { + const params = new Params(); + params.addSubParam(2); + params.addSubParam(3); + assert.equal(params.length, 0); + assert.equal(params.subParamsLength, 0); + assert.deepEqual(params.toArray(), []); + params.addParam(1); + params.addSubParam(2); + params.addSubParam(3); + assert.equal(params.length, 1); + assert.equal(params.subParamsLength, 2); + assert.deepEqual(params.toArray(), [1, [2, 3]]); + }); + it('reset', () => { + const params = new Params(); + params.addParam(1); + params.addSubParam(2); + params.addSubParam(3); + params.addParam(12345); + params.addSubParam(-1); + params.reset(); + assert.equal(params.length, 0); + assert.equal(params.subParamsLength, 0); + assert.deepEqual(params.toArray(), []); + params.addParam(1); + params.addSubParam(2); + params.addSubParam(3); + params.addParam(12345); + params.addSubParam(-1); + assert.equal(params.length, 2); + assert.equal(params.subParamsLength, 3); + assert.deepEqual(params.toArray(), [1, [2, 3], 12345, [-1]]); + }); + it('Params.fromArray --> toArray', () => { + let data: (number | number[])[] = []; + assert.deepEqual(Params.fromArray(data).toArray(), data); + data = [1, [2, 3], 12345, [-1]]; + assert.deepEqual(Params.fromArray(data).toArray(), data); + data = [38, 2, 50, 100, 150]; + assert.deepEqual(Params.fromArray(data).toArray(), data); + data = [38, 2, 50, 100, [150]]; + assert.deepEqual(Params.fromArray(data).toArray(), data); + data = [38, [2, 50, 100, 150]]; + assert.deepEqual(Params.fromArray(data).toArray(), data); + // strip empty sub params + data = [38, [2, 50, 100, 150], 5, [], 6]; + assert.deepEqual(Params.fromArray(data).toArray(), [38, [2, 50, 100, 150], 5, 6]); + }); + it('hasSubParams / getSubParams', () => { + const params = Params.fromArray([38, [2, 50, 100, 150], 5, [], 6]); + assert.equal(params.hasSubParams(0), true); + assert.deepEqual(params.getSubParams(0), new Int16Array([2, 50, 100, 150])); + assert.equal(params.hasSubParams(1), false); + assert.deepEqual(params.getSubParams(1), null); + assert.equal(params.hasSubParams(2), false); + assert.deepEqual(params.getSubParams(2), null); + }); + it('getSubParamsAll', () => { + const params = Params.fromArray([1, [2, 3], 7, 12345, [-1]]); + assert.deepEqual(params.getSubParamsAll(), {0: new Int16Array([2, 3]), 2: new Int16Array([-1])}); + }); + describe('parse tests', () => { + it('param defaults to 0 (ZDM - zero default mode)', () => { + const params = new Params(); + parse(params, ''); + assert.deepEqual(params.toArray(), [0]); + }); + it('sub param defaults to -1', () => { + const params = new Params(); + parse(params, ':'); + assert.deepEqual(params.toArray(), [0, [-1]]); + }); + it('should correctly reset on new sequence', () => { + const params = new Params(); + parse(params, '1;2;3'); + assert.deepEqual(params.toArray(), [1, 2, 3]); + parse(params, '4'); + assert.deepEqual(params.toArray(), [4]); + parse(params, '4::123:5;6;7'); + assert.deepEqual(params.toArray(), [4, [-1, 123, 5], 6, 7]); + parse(params, ''); + assert.deepEqual(params.toArray(), [0]); + }); + it('should handle length restrictions correctly', () => { + // restrict to 3 params and 3 sub params + const params = new Params(3, 3); + parse(params, '1;2;3'); + assert.deepEqual(params.toArray(), [1, 2, 3]); + parse(params, '4'); + assert.deepEqual(params.toArray(), [4]); + parse(params, '4::123:5;6;7'); + assert.deepEqual(params.toArray(), [4, [-1, 123, 5], 6, 7]); + parse(params, ''); + assert.deepEqual(params.toArray(), [0]); + // overlong params + parse(params, '1;2;3;4;5;6;7'); + assert.deepEqual(params.toArray(), [1, 2, 3]); + // overlong sub params + parse(params, '4;38:2::50:100:150;48:5:22'); + assert.deepEqual(params.toArray(), [4, 38, [2, -1, 50], 48]); + }); + it('typical sequences', () => { + const params = new Params(); + // SGR with semicolon syntax + parse(params, '0;4;38;2;50;100;150;48;5;22'); + assert.deepEqual(params.toArray(), [0, 4, 38, 2, 50, 100, 150, 48, 5, 22]); + // SGR mixed style (partly wrong) + parse(params, '0;4;38;2;50:100:150;48;5:22'); + assert.deepEqual(params.toArray(), [0, 4, 38, 2, 50, [100, 150], 48, 5, [22]]); + // SGR colon style + parse(params, '0;4;38:2::50:100:150;48:5:22'); + assert.deepEqual(params.toArray(), [0, 4, 38, [2, -1, 50, 100, 150], 48, [5, 22]]); + }); + }); +}); diff --git a/src/common/parser/Params.ts b/src/common/parser/Params.ts new file mode 100644 index 00000000..f9031b77 --- /dev/null +++ b/src/common/parser/Params.ts @@ -0,0 +1,238 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ +import { StringToUtf32 } from 'common/input/TextDecoder'; + +// TODO: move to Types +interface IParamsConstructor { + new(maxLength: number, maxSubParamsLength: number): IParams; + fromArray(values: (number | number[])[]): IParams; +} + +interface IParams { + /** from ctor */ + maxLength: number; + maxSubParamsLength: number; + + /** param values and its length */ + params: Int16Array; + length: number; + + /** sub params and its length */ + subParams: Int16Array; + subParamsLength: number; + + /** methods */ + clone(): IParams; + toArray(): (number | number[])[]; + reset(): void; + addParam(value: number): void; + addSubParam(value: number): void; + hasSubParams(idx: number): boolean; + getSubParams(idx: number): Int16Array | null; + getSubParamsAll(): {[idx: number]: Int16Array}; +} + +/** + * Params storage class. + * This type is used by the parser to acuumulate sequence parameters and sub parameters + * and transmit them to the input handler actions. + * Note: The params object for the handler actions is borrowed from the parser + * and will be lost after the handler exits. Use either `toArray` or `clone` to get + * a stable copy of the data. + */ +export class Params implements IParams { + // params store and length + public params: Int16Array; + public length: number; + + // sub params store and length + public subParams: Int16Array; + public subParamsLength: number; + + // sub params offsets from param: param idx --> [start, end] offset + private _subParamsIdx: Uint16Array; + private _rejectDigits: boolean; + private _rejectSubDigits: boolean; + + /** + * Create a `Params` type from JS array representation. + */ + public static fromArray(values: (number | number[])[]): Params { + const params = new Params(); + if (!values.length) { + return params; + } + // skip leading sub params + for (let i = (values[0] instanceof Array) ? 1 : 0; i < values.length; ++i) { + const value = values[i]; + if (value instanceof Array) { + for (let k = 0; k < value.length; ++k) { + params.addSubParam(value[k]); + } + } else { + params.addParam(value); + } + } + return params; + } + + /** + * @param maxLength max length of storable parameters + * @param maxSubParamsLength max length of storable sub parameters + */ + constructor(public maxLength: number = 32, public maxSubParamsLength: number = 32) { + // precondition: subparams cannot be more than 256 + if (maxSubParamsLength > 256) { + throw new Error('maxSubParamsLength must not be greater than 256'); + } + this.params = new Int16Array(maxLength); + this.length = 0; + this.subParams = new Int16Array(maxSubParamsLength); + this.subParamsLength = 0; + this._subParamsIdx = new Uint16Array(maxLength); + this._rejectDigits = false; + this._rejectSubDigits = false; + } + + /** + * Clone object to its own copy. + */ + public clone(): Params { + const newParams = new Params(this.maxLength, this.maxSubParamsLength); + newParams.params.set(this.params); + newParams.length = this.length; + newParams.subParams.set(this.subParams); + newParams.subParamsLength = this.subParamsLength; + newParams._subParamsIdx.set(this._subParamsIdx); + return newParams; + } + + /** + * Get a JS array representation of the current parameters and sub parameters. + * The array is structured as follows: + * sequence: "1;2:3:4;5::6" + * array : [1, 2, [3, 4], 5, [-1, 6]] + */ + public toArray(): (number | number[])[] { + const res: (number | number[])[] = []; + for (let i = 0; i < this.length; ++i) { + res.push(this.params[i]); + const start = this._subParamsIdx[i] >> 8; + const end = this._subParamsIdx[i] & 0xFF; + if (end - start > 0) { + res.push(Array.prototype.slice.call(this.subParams, start, end)); + } + } + return res; + } + + /** + * Reset to initial empty state. + */ + public reset(): void { + this.length = 0; + this.subParamsLength = 0; + this._rejectDigits = false; + this._rejectSubDigits = false; + } + + /** + * Add a parameter value. + * `Params` only stores up to `maxLength` parameters, any later + * parameter will be ignored. + * Note: VT devices only stored up to 16 values, xterm seems to + * store up to 30. + */ + public addParam(value: number): void { + if (this.length >= this.maxLength) { + this._rejectDigits = true; + return; + } + this._subParamsIdx[this.length] = this.subParamsLength << 8 | this.subParamsLength; + this.params[this.length++] = value; + } + + /** + * Add a sub parameter value. + * The sub parameter is automatically associated with the last parameter value. + * If there is no parameter yet the sub parameter is ingored. + * `Params` only stores up to `subParamsLength` sub parameters, any later + * sub parameter will be ignored. + */ + public addSubParam(value: number): void { + if (!this.length || this.subParamsLength >= this.maxSubParamsLength) { + this._rejectSubDigits = true; + return; + } + this.subParams[this.subParamsLength++] = value; + this._subParamsIdx[this.length - 1]++; + } + + /** + * Whether parameter at index `idx` has sub parameters. + */ + public hasSubParams(idx: number): boolean { + return ((this._subParamsIdx[idx] & 0xFF) - (this._subParamsIdx[idx] >> 8) > 0); + } + + /** + * Return sub parameters for parameter at index `idx`. + * Note: The values are borrowed, thus you need to copy + * the values if you need to hold them in nonlocal scope. + */ + public getSubParams(idx: number): Int16Array | null { + const start = this._subParamsIdx[idx] >> 8; + const end = this._subParamsIdx[idx] & 0xFF; + if (end - start > 0) { + return this.subParams.subarray(start, end); + } + return null; + } + + /** + * Return all kown sub parameters as {idx: subparams} mapping. + * Note: The values are not borrowed, thus it is safe to hold + * them without copying. + */ + public getSubParamsAll(): {[idx: number]: Int16Array} { + const result: {[idx: number]: Int16Array} = {}; + for (let i = 0; i < this.length; ++i) { + const start = this._subParamsIdx[i] >> 8; + const end = this._subParamsIdx[i] & 0xFF; + if (end - start > 0) { + result[i] = this.subParams.slice(start, end); + } + } + return result; + } + + /** + * Add a single digit value to current parameter. + * This is used by the parser to account digits on a char by char basis. + * Do not use this method directly, consider using `addParam` instead. + */ + public addParamDigit(value: number): void { + if (this._rejectDigits) { + return; + } + this.params[this.length - 1] = this.params[this.length - 1] * 10 + value; + } + + /** + * Add a single digit value to current sub parameter. + * This is used by the parser to account digits on a char by char basis. + * Do not use this method directly, consider using `addSubParam` instead. + */ + public addSubParamDigit(value: number): void { + if (!this.subParamsLength || this._rejectDigits || this._rejectSubDigits) { + return; + } + if (this.subParams[this.subParamsLength - 1] === -1) { + this.subParams[this.subParamsLength - 1] = value; + } else { + this.subParams[this.subParamsLength - 1] = this.subParams[this.subParamsLength - 1] * 10 + value; + } + } +} From 24c39f664da86dd834cf9ab6c525ac4019ead0fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 17 Jun 2019 20:55:29 +0200 Subject: [PATCH 02/25] remove unused import --- src/common/parser/Params.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/common/parser/Params.ts b/src/common/parser/Params.ts index f9031b77..bc1880ed 100644 --- a/src/common/parser/Params.ts +++ b/src/common/parser/Params.ts @@ -2,7 +2,6 @@ * Copyright (c) 2019 The xterm.js authors. All rights reserved. * @license MIT */ -import { StringToUtf32 } from 'common/input/TextDecoder'; // TODO: move to Types interface IParamsConstructor { From cec1aa3f334882a563df2ac28a81b86dbcb1f388 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 17 Jun 2019 21:17:56 +0200 Subject: [PATCH 03/25] interfaces, add test for clone --- src/common/parser/Params.test.ts | 4 ++++ src/common/parser/Params.ts | 31 +---------------------------- src/common/parser/Types.d.ts | 34 ++++++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 30 deletions(-) diff --git a/src/common/parser/Params.test.ts b/src/common/parser/Params.test.ts index 564b0d56..4b45ba3f 100644 --- a/src/common/parser/Params.test.ts +++ b/src/common/parser/Params.test.ts @@ -113,6 +113,10 @@ describe('Params', () => { data = [38, [2, 50, 100, 150], 5, [], 6]; assert.deepEqual(Params.fromArray(data).toArray(), [38, [2, 50, 100, 150], 5, 6]); }); + it('clone', () => { + const params = Params.fromArray([38, [2, 50, 100, 150], 5, [], 6, 1, [2, 3], 12345, [-1]]); + assert.deepEqual(params.clone(), params); + }); it('hasSubParams / getSubParams', () => { const params = Params.fromArray([38, [2, 50, 100, 150], 5, [], 6]); assert.equal(params.hasSubParams(0), true); diff --git a/src/common/parser/Params.ts b/src/common/parser/Params.ts index bc1880ed..74cd5a79 100644 --- a/src/common/parser/Params.ts +++ b/src/common/parser/Params.ts @@ -2,36 +2,7 @@ * Copyright (c) 2019 The xterm.js authors. All rights reserved. * @license MIT */ - -// TODO: move to Types -interface IParamsConstructor { - new(maxLength: number, maxSubParamsLength: number): IParams; - fromArray(values: (number | number[])[]): IParams; -} - -interface IParams { - /** from ctor */ - maxLength: number; - maxSubParamsLength: number; - - /** param values and its length */ - params: Int16Array; - length: number; - - /** sub params and its length */ - subParams: Int16Array; - subParamsLength: number; - - /** methods */ - clone(): IParams; - toArray(): (number | number[])[]; - reset(): void; - addParam(value: number): void; - addSubParam(value: number): void; - hasSubParams(idx: number): boolean; - getSubParams(idx: number): Int16Array | null; - getSubParamsAll(): {[idx: number]: Int16Array}; -} +import { IParams } from 'common/parser/Types'; /** * Params storage class. diff --git a/src/common/parser/Types.d.ts b/src/common/parser/Types.d.ts index 47ec98e1..ffd175d6 100644 --- a/src/common/parser/Types.d.ts +++ b/src/common/parser/Types.d.ts @@ -6,6 +6,40 @@ import { IDisposable } from 'common/Types'; import { ParserState } from 'common/parser/Constants'; +/** + * Params types. + */ +export interface IParamsConstructor { + new(maxLength: number, maxSubParamsLength: number): IParams; + + /** create params object from array like [1, [2, 3]] */ + fromArray(values: (number | number[])[]): IParams; +} + +export interface IParams { + /** from ctor */ + maxLength: number; + maxSubParamsLength: number; + + /** param values and its length */ + params: Int16Array; + length: number; + + /** sub params and its length */ + subParams: Int16Array; + subParamsLength: number; + + /** methods */ + clone(): IParams; + toArray(): (number | number[])[]; + reset(): void; + addParam(value: number): void; + addSubParam(value: number): void; + hasSubParams(idx: number): boolean; + getSubParams(idx: number): Int16Array | null; + getSubParamsAll(): {[idx: number]: Int16Array}; +} + /** * Internal state of EscapeSequenceParser. * Used as argument of the error handler to allow From c5385a640d8a41d9f81fa67eb71ca558ea67342e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 17 Jun 2019 21:54:51 +0200 Subject: [PATCH 04/25] add ":" to param transitions, fix parse loop and tests --- .../parser/EscapeSequenceParser.test.ts | 36 +++++++--- src/common/parser/EscapeSequenceParser.ts | 66 +++++++++++-------- 2 files changed, 66 insertions(+), 36 deletions(-) diff --git a/src/common/parser/EscapeSequenceParser.test.ts b/src/common/parser/EscapeSequenceParser.test.ts index f445efc4..f6e46a76 100644 --- a/src/common/parser/EscapeSequenceParser.test.ts +++ b/src/common/parser/EscapeSequenceParser.test.ts @@ -3,11 +3,12 @@ * @license MIT */ -import { IDcsHandler, IParsingState } from 'common/parser/Types'; +import { IDcsHandler, IParsingState, IParams } from 'common/parser/Types'; import { EscapeSequenceParser, TransitionTable, VT500_TRANSITION_TABLE } from 'common/parser/EscapeSequenceParser'; import * as chai from 'chai'; import { StringToUtf32, stringFromCodePoint } from 'common/input/TextDecoder'; import { ParserState } from 'common/parser/Constants'; +import { Params } from 'common/parser/Params'; function r(a: number, b: number): string[] { let c = b - a; @@ -27,10 +28,13 @@ class TestEscapeSequenceParser extends EscapeSequenceParser { this._osc = value; } public get params(): number[] { - return this._params; + return this._params.toArray() as number[]; } public set params(value: number[]) { - this._params = value; + this._params = Params.fromArray(value); + } + public get realParams(): IParams { + return this._params; } public get collect(): string { return this._collect; @@ -562,16 +566,17 @@ describe('EscapeSequenceParser', function (): void { testTerminal.clear(); } }); - it('trans CSI_ENTRY --> CSI_IGNORE', function (): void { + it('trans CSI_ENTRY --> CSI_PARAM for ":" (0x3a)', function (): void { parser.reset(); parser.currentState = ParserState.CSI_ENTRY; parse(parser, '\x3a'); - chai.expect(parser.currentState).equal(ParserState.CSI_IGNORE); + chai.expect(parser.currentState).equal(ParserState.CSI_PARAM); parser.reset(); }); + /* it('trans CSI_PARAM --> CSI_IGNORE', function (): void { parser.reset(); - const chars = ['\x3a', '\x3c', '\x3d', '\x3e', '\x3f']; + const chars = ['\x3c', '\x3d', '\x3e', '\x3f']; for (let i = 0; i < chars.length; ++i) { parser.currentState = ParserState.CSI_PARAM; parse(parser, '\x3b' + chars[i]); @@ -580,6 +585,19 @@ describe('EscapeSequenceParser', function (): void { parser.reset(); } }); + */ + it('trans CSI_PARAM --> CSI_IGNORE', function (): void { + parser.reset(); + const chars = ['\x3c', '\x3d', '\x3e', '\x3f']; + for (let i = 0; i < chars.length; ++i) { + chai.expect(parser.params).eql([0]); + parser.currentState = ParserState.CSI_PARAM; + parse(parser, '\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(); const chars = r(0x30, 0x40); @@ -785,16 +803,16 @@ describe('EscapeSequenceParser', function (): void { chai.expect(parser.params).eql([0, 0]); parser.reset(); }); - it('trans DCS_ENTRY --> DCS_IGNORE', function (): void { + it('trans DCS_ENTRY --> DCS_PARAM for ":" (0x3a)', function (): void { parser.reset(); parser.currentState = ParserState.DCS_ENTRY; parse(parser, '\x3a'); - chai.expect(parser.currentState).equal(ParserState.DCS_IGNORE); + chai.expect(parser.currentState).equal(ParserState.DCS_PARAM); parser.reset(); }); it('trans DCS_PARAM --> DCS_IGNORE', function (): void { parser.reset(); - const chars = ['\x3a', '\x3c', '\x3d', '\x3e', '\x3f']; + const chars = ['\x3c', '\x3d', '\x3e', '\x3f']; for (let i = 0; i < chars.length; ++i) { parser.currentState = ParserState.DCS_PARAM; parse(parser, '\x3b' + chars[i]); diff --git a/src/common/parser/EscapeSequenceParser.ts b/src/common/parser/EscapeSequenceParser.ts index d8d4e02e..843eb4c9 100644 --- a/src/common/parser/EscapeSequenceParser.ts +++ b/src/common/parser/EscapeSequenceParser.ts @@ -9,6 +9,7 @@ import { Disposable } from 'common/Lifecycle'; import { utf32ToString } from 'common/input/TextDecoder'; import { IDisposable } from 'common/Types'; import { fill } from 'common/TypedArrayUtils'; +import { Params } from 'common/parser/Params'; interface IHandlerCollection { [key: string]: T[]; @@ -143,17 +144,14 @@ export const VT500_TRANSITION_TABLE = (function (): TransitionTable { // 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(r(0x30, 0x3c), 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(0x30, 0x3c), 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([0x3c, 0x3d, 0x3e, 0x3f], ParserState.CSI_PARAM, ParserAction.IGNORE, 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); 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); @@ -173,9 +171,7 @@ export const VT500_TRANSITION_TABLE = (function (): TransitionTable { 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(r(0x30, 0x3c), 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); @@ -183,9 +179,8 @@ export const VT500_TRANSITION_TABLE = (function (): TransitionTable { 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(0x30, 0x3c), ParserState.DCS_PARAM, ParserAction.PARAM, ParserState.DCS_PARAM); + table.addMany([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); @@ -236,7 +231,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP // buffers over several parse calls protected _osc: string; - protected _params: number[]; + protected _params: Params; protected _collect: string; // handler lookup containers @@ -264,7 +259,8 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP this.initialState = ParserState.GROUND; this.currentState = this.initialState; this._osc = ''; - this._params = [0]; + this._params = new Params(); + this._params.addParam(0); this._collect = ''; this.precedingCodepoint = 0; @@ -394,7 +390,8 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP reset(): void { this.currentState = this.initialState; this._osc = ''; - this._params = [0]; + this._params.reset(); + this._params.addParam(0); this._collect = ''; this._activeDcsHandler = null; this.precedingCodepoint = 0; @@ -418,7 +415,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP let currentState = this.currentState; let osc = this._osc; let collect = this._collect; - let params = this._params; + const params = this._params; const table: Uint8Array = this.TRANSITIONS.table; let dcsHandler: IDcsHandler | null = this._activeDcsHandler; let callback: Function | null = null; @@ -472,7 +469,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP currentState, osc, collect, - params, + params: params.toArray() as number[], abort: false }); if (inject.abort) return; @@ -488,20 +485,32 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP let j = handlers ? handlers.length - 1 : -1; for (; j >= 0; j--) { // undefined or true means success and to stop bubbling - if (handlers[j](params, collect) !== false) { + if (handlers[j](params.toArray() as number[], collect) !== false) { break; } } if (j < 0) { - this._csiHandlerFb(collect, params, code); + this._csiHandlerFb(collect, params.toArray() as number[], code); } break; case ParserAction.PARAM: - // inner loop: digits (0x30 - 0x39) and ; (0x3b) + // inner loop: digits (0x30 - 0x39) and ; (0x3b) and : (0x3a) + let isSub = false; do { - if (code === 0x3b) params.push(0); - else params[params.length - 1] = params[params.length - 1] * 10 + code - 48; - } while (++i < length && (code = data[i]) > 0x2f && (code < 0x3a || code === 0x3b)); + switch (code) { + case 0x3b: + params.addParam(0); + isSub = false; + break; + case 0x3a: + params.addSubParam(-1); + isSub = true; + break; + default: // 0x30 - 0x39 + if (isSub) params.addSubParamDigit(code - 48); + else params.addParamDigit(code - 48); + } + } while (++i < length && (code = data[i]) > 0x2f && code < 0x3c); i--; break; case ParserAction.COLLECT: @@ -515,14 +524,15 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP break; case ParserAction.CLEAR: osc = ''; - params = [0]; + params.reset(); + params.addParam(0); collect = ''; break; case ParserAction.DCS_HOOK: this.precedingCodepoint = 0; dcsHandler = this._dcsHandlers[collect + String.fromCharCode(code)]; if (!dcsHandler) dcsHandler = this._dcsHandlerFb; - dcsHandler.hook(collect, params, code); + dcsHandler.hook(collect, params.toArray() as number[], code); break; case ParserAction.DCS_PUT: // inner loop - exit DCS_PUT: 0x18, 0x1a, 0x1b, 0x7f, 0x80 - 0x9f @@ -544,7 +554,8 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP } if (code === 0x1b) transition |= ParserState.ESCAPE; osc = ''; - params = [0]; + params.reset(); + params.addParam(0); collect = ''; break; case ParserAction.OSC_START: @@ -590,7 +601,8 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP } if (code === 0x1b) transition |= ParserState.ESCAPE; osc = ''; - params = [0]; + params.reset(); + params.addParam(0); collect = ''; break; } From 1dd63a6d66511f69aa6e11042e32098715186c78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 17 Jun 2019 23:31:54 +0200 Subject: [PATCH 05/25] apply type over codebase --- src/InputHandler.test.ts | 55 ++-- src/InputHandler.ts | 257 +++++++----------- src/Terminal.ts | 3 +- src/TestUtils.test.ts | 3 +- src/Types.d.ts | 75 ++--- .../parser/EscapeSequenceParser.test.ts | 50 ++-- src/common/parser/EscapeSequenceParser.ts | 22 +- src/common/parser/Types.d.ts | 10 +- src/public/Terminal.ts | 4 +- typings/xterm.d.ts | 26 +- 10 files changed, 243 insertions(+), 262 deletions(-) diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index 19d5f6a2..73365af8 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -12,6 +12,7 @@ import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { CellData } from 'common/buffer/CellData'; import { Attributes } from 'common/buffer/Constants'; import { AttributeData } from 'common/buffer/AttributeData'; +import { Params } from 'common/parser/Params'; describe('InputHandler', () => { describe('save and restore cursor', () => { @@ -22,7 +23,7 @@ describe('InputHandler', () => { terminal.curAttrData.fg = 3; const inputHandler = new InputHandler(terminal); // Save cursor position - inputHandler.saveCursor([]); + inputHandler.saveCursor(); assert.equal(terminal.buffer.x, 1); assert.equal(terminal.buffer.y, 2); assert.equal(terminal.curAttrData.fg, 3); @@ -31,7 +32,7 @@ describe('InputHandler', () => { terminal.buffer.y = 20; terminal.curAttrData.fg = 30; // Restore cursor position - inputHandler.restoreCursor([]); + inputHandler.restoreCursor(); assert.equal(terminal.buffer.x, 1); assert.equal(terminal.buffer.y, 2); assert.equal(terminal.curAttrData.fg, 3); @@ -42,37 +43,37 @@ describe('InputHandler', () => { const inputHandler = new InputHandler(terminal); const collect = ' '; - inputHandler.setCursorStyle([0], collect); + inputHandler.setCursorStyle(Params.fromArray([0]), collect); assert.equal(terminal.options['cursorStyle'], 'block'); assert.equal(terminal.options['cursorBlink'], true); terminal.options = {}; - inputHandler.setCursorStyle([1], collect); + inputHandler.setCursorStyle(Params.fromArray([1]), collect); assert.equal(terminal.options['cursorStyle'], 'block'); assert.equal(terminal.options['cursorBlink'], true); terminal.options = {}; - inputHandler.setCursorStyle([2], collect); + inputHandler.setCursorStyle(Params.fromArray([2]), collect); assert.equal(terminal.options['cursorStyle'], 'block'); assert.equal(terminal.options['cursorBlink'], false); terminal.options = {}; - inputHandler.setCursorStyle([3], collect); + inputHandler.setCursorStyle(Params.fromArray([3]), collect); assert.equal(terminal.options['cursorStyle'], 'underline'); assert.equal(terminal.options['cursorBlink'], true); terminal.options = {}; - inputHandler.setCursorStyle([4], collect); + inputHandler.setCursorStyle(Params.fromArray([4]), collect); assert.equal(terminal.options['cursorStyle'], 'underline'); assert.equal(terminal.options['cursorBlink'], false); terminal.options = {}; - inputHandler.setCursorStyle([5], collect); + inputHandler.setCursorStyle(Params.fromArray([5]), collect); assert.equal(terminal.options['cursorStyle'], 'bar'); assert.equal(terminal.options['cursorBlink'], true); terminal.options = {}; - inputHandler.setCursorStyle([6], collect); + inputHandler.setCursorStyle(Params.fromArray([6]), collect); assert.equal(terminal.options['cursorStyle'], 'bar'); assert.equal(terminal.options['cursorBlink'], false); }); @@ -84,10 +85,10 @@ describe('InputHandler', () => { terminal.bracketedPasteMode = false; const inputHandler = new InputHandler(terminal); // Set bracketed paste mode - inputHandler.setMode([2004], collect); + inputHandler.setMode(Params.fromArray([2004]), collect); assert.equal(terminal.bracketedPasteMode, true); // Reset bracketed paste mode - inputHandler.resetMode([2004], collect); + inputHandler.resetMode(Params.fromArray([2004]), collect); assert.equal(terminal.bracketedPasteMode, false); }); }); @@ -113,25 +114,25 @@ describe('InputHandler', () => { // insert one char from params = [0] term.buffer.y = 0; term.buffer.x = 70; - inputHandler.insertChars([0]); + inputHandler.insertChars(Params.fromArray([0])); expect(line1.translateToString(false)).equals(Array(term.cols - 9).join('a') + ' 123456789'); // insert one char from params = [1] term.buffer.y = 0; term.buffer.x = 70; - inputHandler.insertChars([1]); + inputHandler.insertChars(Params.fromArray([1])); expect(line1.translateToString(false)).equals(Array(term.cols - 9).join('a') + ' 12345678'); // insert two chars from params = [2] term.buffer.y = 0; term.buffer.x = 70; - inputHandler.insertChars([2]); + inputHandler.insertChars(Params.fromArray([2])); expect(line1.translateToString(false)).equals(Array(term.cols - 9).join('a') + ' 123456'); // insert 10 chars from params = [10] term.buffer.y = 0; term.buffer.x = 70; - inputHandler.insertChars([10]); + inputHandler.insertChars(Params.fromArray([10])); expect(line1.translateToString(false)).equals(Array(term.cols - 9).join('a') + ' '); expect(line1.translateToString(true)).equals(Array(term.cols - 9).join('a')); }); @@ -150,28 +151,28 @@ describe('InputHandler', () => { // delete one char from params = [0] term.buffer.y = 0; term.buffer.x = 70; - inputHandler.deleteChars([0]); + inputHandler.deleteChars(Params.fromArray([0])); expect(line1.translateToString(false)).equals(Array(term.cols - 9).join('a') + '234567890 '); expect(line1.translateToString(true)).equals(Array(term.cols - 9).join('a') + '234567890'); // insert one char from params = [1] term.buffer.y = 0; term.buffer.x = 70; - inputHandler.deleteChars([1]); + inputHandler.deleteChars(Params.fromArray([1])); expect(line1.translateToString(false)).equals(Array(term.cols - 9).join('a') + '34567890 '); expect(line1.translateToString(true)).equals(Array(term.cols - 9).join('a') + '34567890'); // insert two chars from params = [2] term.buffer.y = 0; term.buffer.x = 70; - inputHandler.deleteChars([2]); + inputHandler.deleteChars(Params.fromArray([2])); expect(line1.translateToString(false)).equals(Array(term.cols - 9).join('a') + '567890 '); expect(line1.translateToString(true)).equals(Array(term.cols - 9).join('a') + '567890'); // insert 10 chars from params = [10] term.buffer.y = 0; term.buffer.x = 70; - inputHandler.deleteChars([10]); + inputHandler.deleteChars(Params.fromArray([10])); expect(line1.translateToString(false)).equals(Array(term.cols - 9).join('a') + ' '); expect(line1.translateToString(true)).equals(Array(term.cols - 9).join('a')); }); @@ -187,19 +188,19 @@ describe('InputHandler', () => { // params[0] - right erase term.buffer.y = 0; term.buffer.x = 70; - inputHandler.eraseInLine([0]); + inputHandler.eraseInLine(Params.fromArray([0])); expect(term.buffer.lines.get(0).translateToString(false)).equals(Array(71).join('a') + ' '); // params[1] - left erase term.buffer.y = 1; term.buffer.x = 70; - inputHandler.eraseInLine([1]); + inputHandler.eraseInLine(Params.fromArray([1])); expect(term.buffer.lines.get(1).translateToString(false)).equals(Array(71).join(' ') + ' aaaaaaaaa'); // params[1] - left erase term.buffer.y = 2; term.buffer.x = 70; - inputHandler.eraseInLine([2]); + inputHandler.eraseInLine(Params.fromArray([2])); expect(term.buffer.lines.get(2).translateToString(false)).equals(Array(term.cols + 1).join(' ')); }); @@ -213,7 +214,7 @@ describe('InputHandler', () => { // params [0] - right and below erase term.buffer.y = 5; term.buffer.x = 40; - inputHandler.eraseInDisplay([0]); + inputHandler.eraseInDisplay(Params.fromArray([0])); expect(termContent(term, false)).eql([ Array(term.cols + 1).join('a'), Array(term.cols + 1).join('a'), @@ -241,7 +242,7 @@ describe('InputHandler', () => { // params [1] - left and above term.buffer.y = 5; term.buffer.x = 40; - inputHandler.eraseInDisplay([1]); + inputHandler.eraseInDisplay(Params.fromArray([1])); expect(termContent(term, false)).eql([ Array(term.cols + 1).join(' '), Array(term.cols + 1).join(' '), @@ -269,7 +270,7 @@ describe('InputHandler', () => { // params [2] - whole screen term.buffer.y = 5; term.buffer.x = 40; - inputHandler.eraseInDisplay([2]); + inputHandler.eraseInDisplay(Params.fromArray([2])); expect(termContent(term, false)).eql([ Array(term.cols + 1).join(' '), Array(term.cols + 1).join(' '), @@ -301,7 +302,7 @@ describe('InputHandler', () => { expect(term.buffer.lines.get(2).isWrapped).true; term.buffer.y = 2; term.buffer.x = 40; - inputHandler.eraseInDisplay([1]); + inputHandler.eraseInDisplay(Params.fromArray([1])); expect(term.buffer.lines.get(2).isWrapped).false; // reset and add a wrapped line @@ -316,7 +317,7 @@ describe('InputHandler', () => { expect(term.buffer.lines.get(2).isWrapped).true; term.buffer.y = 1; term.buffer.x = 90; // Cursor is beyond last column - inputHandler.eraseInDisplay([1]); + inputHandler.eraseInDisplay(Params.fromArray([1])); expect(term.buffer.lines.get(2).isWrapped).false; }); }); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index a2ae4e08..b5c83293 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -15,7 +15,7 @@ import { concat } from 'common/TypedArrayUtils'; import { StringToUtf32, stringFromCodePoint, utf32ToString, Utf8ToUtf32 } from 'common/input/TextDecoder'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { EventEmitter, IEvent } from 'common/EventEmitter'; -import { IParsingState, IDcsHandler, IEscapeSequenceParser } from 'common/parser/Types'; +import { IParsingState, IDcsHandler, IEscapeSequenceParser, IParams } from 'common/parser/Types'; import { NULL_CELL_CODE, NULL_CELL_WIDTH, Attributes, FgFlags, BgFlags } from 'common/buffer/Constants'; import { CellData } from 'common/buffer/CellData'; import { AttributeData } from 'common/buffer/AttributeData'; @@ -41,7 +41,7 @@ class DECRQSS implements IDcsHandler { constructor(private _terminal: any) { } - hook(collect: string, params: number[], flag: number): void { + hook(collect: string, params: IParams, flag: number): void { this._data = new Uint32Array(0); } @@ -131,8 +131,8 @@ export class InputHandler extends Disposable implements IInputHandler { /** * custom fallback handlers */ - this._parser.setCsiHandlerFallback((collect: string, params: number[], flag: number) => { - this._terminal.error('Unknown CSI code: ', { collect, params, flag: String.fromCharCode(flag) }); + this._parser.setCsiHandlerFallback((collect: string, params: IParams, flag: number) => { + this._terminal.error('Unknown CSI code: ', { collect, params: params.toArray(), flag: String.fromCharCode(flag) }); }); this._parser.setEscHandlerFallback((collect: string, flag: number) => { this._terminal.error('Unknown ESC code: ', { collect, flag: String.fromCharCode(flag) }); @@ -252,8 +252,8 @@ export class InputHandler extends Disposable implements IInputHandler { /** * ESC handlers */ - this._parser.setEscHandler('7', () => this.saveCursor([])); - this._parser.setEscHandler('8', () => this.restoreCursor([])); + 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()); @@ -476,7 +476,7 @@ export class InputHandler extends Disposable implements IInputHandler { /** * Forward addCsiHandler from parser. */ - public addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable { + public addCsiHandler(flag: string, callback: (params: IParams, collect: string) => boolean): IDisposable { return this._parser.addCsiHandler(flag, callback); } @@ -571,10 +571,10 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Ps @ * Insert Ps (Blank) Character(s) (default = 1) (ICH). */ - public insertChars(params: number[]): void { + public insertChars(params: IParams): void { this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).insertCells( this._terminal.buffer.x, - params[0] || 1, + params.params[0] || 1, this._terminal.buffer.getNullCell(this._terminal.eraseAttrData()) ); this._terminal.updateRange(this._terminal.buffer.y); @@ -584,11 +584,8 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Ps A * Cursor Up Ps Times (default = 1) (CUU). */ - public cursorUp(params: number[]): void { - let param = params[0]; - if (param < 1) { - param = 1; - } + public cursorUp(params: IParams): void { + const param = params.params[0] || 1; this._terminal.buffer.y -= param; if (this._terminal.buffer.y < 0) { this._terminal.buffer.y = 0; @@ -599,11 +596,8 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Ps B * Cursor Down Ps Times (default = 1) (CUD). */ - public cursorDown(params: number[]): void { - let param = params[0]; - if (param < 1) { - param = 1; - } + public cursorDown(params: IParams): void { + const param = params.params[0] || 1; this._terminal.buffer.y += param; if (this._terminal.buffer.y >= this._terminal.rows) { this._terminal.buffer.y = this._terminal.rows - 1; @@ -618,11 +612,8 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Ps C * Cursor Forward Ps Times (default = 1) (CUF). */ - public cursorForward(params: number[]): void { - let param = params[0]; - if (param < 1) { - param = 1; - } + public cursorForward(params: IParams): void { + const param = params.params[0] || 1; this._terminal.buffer.x += param; if (this._terminal.buffer.x >= this._terminal.cols) { this._terminal.buffer.x = this._terminal.cols - 1; @@ -633,11 +624,9 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Ps D * Cursor Backward Ps Times (default = 1) (CUB). */ - public cursorBackward(params: number[]): void { - let param = params[0]; - if (param < 1) { - param = 1; - } + public cursorBackward(params: IParams): void { + const param = params.params[0] || 1; + // If the end of the line is hit, prevent this action from wrapping around to the next line. if (this._terminal.buffer.x >= this._terminal.cols) { this._terminal.buffer.x--; @@ -653,11 +642,8 @@ export class InputHandler extends Disposable implements IInputHandler { * Cursor Next Line Ps Times (default = 1) (CNL). * same as CSI Ps B ? */ - public cursorNextLine(params: number[]): void { - let param = params[0]; - if (param < 1) { - param = 1; - } + public cursorNextLine(params: IParams): void { + const param = params.params[0] || 1; this._terminal.buffer.y += param; if (this._terminal.buffer.y >= this._terminal.rows) { this._terminal.buffer.y = this._terminal.rows - 1; @@ -671,11 +657,8 @@ export class InputHandler extends Disposable implements IInputHandler { * Cursor Preceding Line Ps Times (default = 1) (CNL). * reuse CSI Ps A ? */ - public cursorPrecedingLine(params: number[]): void { - let param = params[0]; - if (param < 1) { - param = 1; - } + public cursorPrecedingLine(params: IParams): void { + const param = params.params[0] || 1; this._terminal.buffer.y -= param; if (this._terminal.buffer.y < 0) { this._terminal.buffer.y = 0; @@ -688,11 +671,8 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Ps G * Cursor Character Absolute [column] (default = [row,1]) (CHA). */ - public cursorCharAbsolute(params: number[]): void { - let param = params[0]; - if (param < 1) { - param = 1; - } + public cursorCharAbsolute(params: IParams): void { + const param = params.params[0] || 1; this._terminal.buffer.x = param - 1; } @@ -700,15 +680,9 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Ps ; Ps H * Cursor Position [row;column] (default = [1,1]) (CUP). */ - public cursorPosition(params: number[]): void { - let col: number; - let row: number = params[0] - 1; - - if (params.length >= 2) { - col = params[1] - 1; - } else { - col = 0; - } + public cursorPosition(params: IParams): void { + let row: number = params.params[0] - 1; + let col: number = (params.length >= 2) ? params.params[1] - 1 : 0; if (row < 0) { row = 0; @@ -730,8 +704,8 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Ps I * Cursor Forward Tabulation Ps tab stops (default = 1) (CHT). */ - public cursorForwardTab(params: number[]): void { - let param = params[0] || 1; + public cursorForwardTab(params: IParams): void { + let param = params.params[0] || 1; while (param--) { this._terminal.buffer.x = this._terminal.buffer.nextStop(); } @@ -777,9 +751,9 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 1 -> Selective Erase Above. * Ps = 2 -> Selective Erase All. */ - public eraseInDisplay(params: number[]): void { + public eraseInDisplay(params: IParams): void { let j; - switch (params[0]) { + switch (params.params[0]) { case 0: j = this._terminal.buffer.y; this._terminal.updateRange(j); @@ -836,8 +810,8 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 1 -> Selective Erase to Left. * Ps = 2 -> Selective Erase All. */ - public eraseInLine(params: number[]): void { - switch (params[0]) { + public eraseInLine(params: IParams): void { + switch (params.params[0]) { case 0: this._eraseInBufferLine(this._terminal.buffer.y, this._terminal.buffer.x, this._terminal.cols); break; @@ -855,11 +829,8 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Ps L * Insert Ps Line(s) (default = 1) (IL). */ - public insertLines(params: number[]): void { - let param: number = params[0]; - if (param < 1) { - param = 1; - } + public insertLines(params: IParams): void { + let param = params.params[0] || 1; // make buffer local for faster access const buffer = this._terminal.buffer; @@ -884,11 +855,8 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Ps M * Delete Ps Line(s) (default = 1) (DL). */ - public deleteLines(params: number[]): void { - let param = params[0]; - if (param < 1) { - param = 1; - } + public deleteLines(params: IParams): void { + let param = params.params[0] || 1; // make buffer local for faster access const buffer = this._terminal.buffer; @@ -914,10 +882,10 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Ps P * Delete Ps Character(s) (default = 1) (DCH). */ - public deleteChars(params: number[]): void { + public deleteChars(params: IParams): void { this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).deleteCells( this._terminal.buffer.x, - params[0] || 1, + params.params[0] || 1, this._terminal.buffer.getNullCell(this._terminal.eraseAttrData()) ); this._terminal.updateRange(this._terminal.buffer.y); @@ -926,8 +894,8 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Ps S Scroll up Ps lines (default = 1) (SU). */ - public scrollUp(params: number[]): void { - let param = params[0] || 1; + public scrollUp(params: IParams): void { + let param = params.params[0] || 1; // make buffer local for faster access const buffer = this._terminal.buffer; @@ -944,9 +912,9 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Ps T Scroll down Ps lines (default = 1) (SD). */ - public scrollDown(params: number[], collect?: string): void { + public scrollDown(params: IParams, collect?: string): void { if (params.length < 2 && !collect) { - let param = params[0] || 1; + let param = params.params[0] || 1; // make buffer local for faster access const buffer = this._terminal.buffer; @@ -965,10 +933,10 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Ps X * Erase Ps Character(s) (default = 1) (ECH). */ - public eraseChars(params: number[]): void { + public eraseChars(params: IParams): void { this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).replaceCells( this._terminal.buffer.x, - this._terminal.buffer.x + (params[0] || 1), + this._terminal.buffer.x + (params.params[0] || 1), this._terminal.buffer.getNullCell(this._terminal.eraseAttrData()) ); this._terminal.updateRange(this._terminal.buffer.y); @@ -977,8 +945,8 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Ps Z Cursor Backward Tabulation Ps tab stops (default = 1) (CBT). */ - public cursorBackwardTab(params: number[]): void { - let param = params[0] || 1; + public cursorBackwardTab(params: IParams): void { + let param = params.params[0] || 1; // make buffer local for faster access const buffer = this._terminal.buffer; @@ -992,11 +960,8 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Pm ` Character Position Absolute * [column] (default = [row,1]) (HPA). */ - public charPosAbsolute(params: number[]): void { - let param = params[0]; - if (param < 1) { - param = 1; - } + public charPosAbsolute(params: IParams): void { + const param = params.params[0] || 1; this._terminal.buffer.x = param - 1; if (this._terminal.buffer.x >= this._terminal.cols) { this._terminal.buffer.x = this._terminal.cols - 1; @@ -1008,11 +973,8 @@ export class InputHandler extends Disposable implements IInputHandler { * [columns] (default = [row,col+1]) (HPR) * reuse CSI Ps C ? */ - public hPositionRelative(params: number[]): void { - let param = params[0]; - if (param < 1) { - param = 1; - } + public hPositionRelative(params: IParams): void { + const param = params.params[0] || 1; this._terminal.buffer.x += param; if (this._terminal.buffer.x >= this._terminal.cols) { this._terminal.buffer.x = this._terminal.cols - 1; @@ -1041,12 +1003,12 @@ export class InputHandler extends Disposable implements IInputHandler { * Note: To get reset on a valid sequence working correctly without much runtime penalty, * the preceding codepoint is stored on the parser in `this.print` and reset during `parser.parse`. */ - public repeatPrecedingCharacter(params: number[]): void { + public repeatPrecedingCharacter(params: IParams): void { if (!this._parser.precedingCodepoint) { return; } // call print to insert the chars and handle correct wrapping - const length = params[0] || 1; + const length = params.params[0] || 1; const data = new Uint32Array(length); for (let i = 0; i < length; ++i) { data[i] = this._parser.precedingCodepoint; @@ -1091,8 +1053,8 @@ export class InputHandler extends Disposable 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[], collect?: string): void { - if (params[0] > 0) { + public sendDeviceAttributes(params: IParams, collect?: string): void { + if (params.params[0] > 0) { return; } @@ -1113,7 +1075,7 @@ export class InputHandler extends Disposable implements IInputHandler { } else if (this._terminal.is('linux')) { // not supported by linux console. // linux console echoes parameters. - this._terminal.handler(params[0] + 'c'); + this._terminal.handler(params.params[0] + 'c'); } else if (this._terminal.is('screen')) { this._terminal.handler(C0.ESC + '[>83;40003;0c'); } @@ -1124,11 +1086,8 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Pm d Vertical Position Absolute (VPA) * [row] (default = [1,column]) */ - public linePosAbsolute(params: number[]): void { - let param = params[0]; - if (param < 1) { - param = 1; - } + public linePosAbsolute(params: IParams): void { + const param = params.params[0] || 1; this._terminal.buffer.y = param - 1; if (this._terminal.buffer.y >= this._terminal.rows) { this._terminal.buffer.y = this._terminal.rows - 1; @@ -1140,11 +1099,8 @@ export class InputHandler extends Disposable implements IInputHandler { * [rows] (default = [row+1,column]) * reuse CSI Ps B ? */ - public vPositionRelative(params: number[]): void { - let param = params[0]; - if (param < 1) { - param = 1; - } + public vPositionRelative(params: IParams): void { + const param = params.params[0] || 1; this._terminal.buffer.y += param; if (this._terminal.buffer.y >= this._terminal.rows) { this._terminal.buffer.y = this._terminal.rows - 1; @@ -1160,16 +1116,16 @@ export class InputHandler extends Disposable implements IInputHandler { * Horizontal and Vertical Position [row;column] (default = * [1,1]) (HVP). */ - public hVPosition(params: number[]): void { - if (params[0] < 1) params[0] = 1; - if (params[1] < 1) params[1] = 1; + public hVPosition(params: IParams): void { + const row = params.params[0] || 1; + const col = (params.length > 1) ? params.params[1] || 1 : 1; - this._terminal.buffer.y = params[0] - 1; + this._terminal.buffer.y = row - 1; if (this._terminal.buffer.y >= this._terminal.rows) { this._terminal.buffer.y = this._terminal.rows - 1; } - this._terminal.buffer.x = params[1] - 1; + this._terminal.buffer.x = col - 1; if (this._terminal.buffer.x >= this._terminal.cols) { this._terminal.buffer.x = this._terminal.cols - 1; } @@ -1183,9 +1139,9 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 2 -> Clear Stops on Line. * http://vt100.net/annarbor/aaa-ug/section6.html */ - public tabClear(params: number[]): void { - const param = params[0]; - if (param <= 0) { + public tabClear(params: IParams): void { + const param = params.params[0]; + if (param === 0) { delete this._terminal.buffer.tabs[this._terminal.buffer.x]; } else if (param === 3) { this._terminal.buffer.tabs = {}; @@ -1278,15 +1234,13 @@ export class InputHandler extends Disposable implements IInputHandler { * Modes: * http: *vt100.net/docs/vt220-rm/chapter4.html */ - public setMode(params: number[], collect?: string): void { - if (params.length > 1) { - for (let i = 0; i < params.length; i++) { - this.setMode([params[i]]); - } - - return; + public setMode(params: IParams, collect?: string): void { + for (let i = 0; i < params.length; i++) { + this._setMode([params.params[i]], collect); } + } + private _setMode(params: number[], collect?: string): void { if (!collect) { switch (params[0]) { case 4: @@ -1380,10 +1334,10 @@ export class InputHandler extends Disposable implements IInputHandler { this._terminal.cursorHidden = false; break; case 1048: // alt screen cursor - this.saveCursor(params); + this.saveCursor(); break; case 1049: // alt screen buffer cursor - this.saveCursor(params); + this.saveCursor(); // FALL-THROUGH case 47: // alt screen buffer case 1047: // alt screen buffer @@ -1483,15 +1437,13 @@ export class InputHandler extends Disposable 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[], collect?: string): void { - if (params.length > 1) { - for (let i = 0; i < params.length; i++) { - this.resetMode([params[i]]); - } - - return; + public resetMode(params: IParams, collect?: string): void { + for (let i = 0; i < params.length; i++) { + this._resetMode([params.params[i]], collect); } + } + private _resetMode(params: number[], collect?: string): void { if (!collect) { switch (params[0]) { case 4: @@ -1559,7 +1511,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._terminal.cursorHidden = true; break; case 1048: // alt screen cursor - this.restoreCursor(params); + this.restoreCursor(); break; case 1049: // alt screen buffer cursor // FALL-THROUGH @@ -1568,7 +1520,7 @@ export class InputHandler extends Disposable implements IInputHandler { // Ensure the selection manager has the correct buffer this._terminal.buffers.activateNormalBuffer(); if (params[0] === 1049) { - this.restoreCursor(params); + this.restoreCursor(); } this._terminal.refresh(0, this._terminal.rows - 1); if (this._terminal.viewport) { @@ -1648,9 +1600,9 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 4 8 ; 5 ; Ps -> Set background color to the second * Ps. */ - public charAttributes(params: number[]): void { + public charAttributes(params: IParams): void { // Optimize a single SGR0. - if (params.length === 1 && params[0] === 0) { + if (params.length === 1 && params.params[0] === 0) { this._terminal.curAttrData.fg = DEFAULT_ATTR_DATA.fg; this._terminal.curAttrData.bg = DEFAULT_ATTR_DATA.bg; return; @@ -1661,7 +1613,7 @@ export class InputHandler extends Disposable implements IInputHandler { const attr = this._terminal.curAttrData; for (let i = 0; i < l; i++) { - p = params[i]; + p = params.params[i]; if (p >= 30 && p <= 37) { // fg color 8 attr.fg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK); @@ -1733,29 +1685,30 @@ export class InputHandler extends Disposable implements IInputHandler { attr.bg |= DEFAULT_ATTR_DATA.bg & (Attributes.PCOLOR_MASK | Attributes.RGB_MASK); } else if (p === 38) { // fg color 256 and RGB - if (params[i + 1] === 2) { + // FIXME: apply sub parameter, avoid reading over length! + if (params.params[i + 1] === 2) { i += 2; attr.fg |= Attributes.CM_RGB; attr.fg &= ~Attributes.RGB_MASK; - attr.fg |= AttributeData.fromColorRGB([params[i], params[i + 1], params[i + 2]]); + attr.fg |= AttributeData.fromColorRGB([params.params[i], params.params[i + 1], params.params[i + 2]]); i += 2; - } else if (params[i + 1] === 5) { + } else if (params.params[i + 1] === 5) { i += 2; - p = params[i] & 0xff; + p = params.params[i] & 0xff; attr.fg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK); attr.fg |= Attributes.CM_P256 | p; } } else if (p === 48) { // bg color 256 and RGB - if (params[i + 1] === 2) { + if (params.params[i + 1] === 2) { i += 2; attr.bg |= Attributes.CM_RGB; attr.bg &= ~Attributes.RGB_MASK; - attr.bg |= AttributeData.fromColorRGB([params[i], params[i + 1], params[i + 2]]); + attr.bg |= AttributeData.fromColorRGB([params.params[i], params.params[i + 1], params.params[i + 2]]); i += 2; - } else if (params[i + 1] === 5) { + } else if (params.params[i + 1] === 5) { i += 2; - p = params[i] & 0xff; + p = params.params[i] & 0xff; attr.bg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK); attr.bg |= Attributes.CM_P256 | p; } @@ -1794,9 +1747,9 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI ? 5 3 n Locator available, if compiled-in, or * CSI ? 5 0 n No Locator, if not. */ - public deviceStatus(params: number[], collect?: string): void { + public deviceStatus(params: IParams, collect?: string): void { if (!collect) { - switch (params[0]) { + switch (params.params[0]) { case 5: // status report this._onData.fire(`${C0.ESC}[0n`); @@ -1811,7 +1764,7 @@ export class InputHandler extends Disposable implements IInputHandler { } else if (collect === '?') { // modern xterm doesnt seem to // respond to any of these except ?6, 6, and 5 - switch (params[0]) { + switch (params.params[0]) { case 6: // cursor position const y = this._terminal.buffer.y + 1; @@ -1842,7 +1795,7 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI ! p Soft terminal reset (DECSTR). * http://vt100.net/docs/vt220-rm/table4-10.html */ - public softReset(params: number[], collect?: string): void { + public softReset(params: IParams, collect?: string): void { if (collect === '!') { this._terminal.cursorHidden = false; this._terminal.insertMode = false; @@ -1873,9 +1826,9 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 5 -> blinking bar (xterm). * Ps = 6 -> steady bar (xterm). */ - public setCursorStyle(params?: number[], collect?: string): void { + public setCursorStyle(params?: IParams, collect?: string): void { if (collect === ' ') { - const param = params[0] < 1 ? 1 : params[0]; + const param = params.params[0] || 1; switch (param) { case 1: case 2: @@ -1901,12 +1854,12 @@ export class InputHandler extends Disposable implements IInputHandler { * dow) (DECSTBM). * CSI ? Pm r */ - public setScrollRegion(params: number[], collect?: string): void { + public setScrollRegion(params: IParams, 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.scrollTop = (params.params[0] || 1) - 1; + this._terminal.buffer.scrollBottom = (params.length > 1 && params.params[1] && params.params[1] <= this._terminal.rows ? params.params[1] : this._terminal.rows) - 1; this._terminal.buffer.x = 0; this._terminal.buffer.y = 0; } @@ -1917,7 +1870,7 @@ export class InputHandler extends Disposable implements IInputHandler { * ESC 7 * Save cursor (ANSI.SYS). */ - public saveCursor(params: number[]): void { + public saveCursor(params?: IParams): void { this._terminal.buffer.savedX = this._terminal.buffer.x; this._terminal.buffer.savedY = this._terminal.buffer.ybase + this._terminal.buffer.y; this._terminal.buffer.savedCurAttrData.fg = this._terminal.curAttrData.fg; @@ -1930,7 +1883,7 @@ export class InputHandler extends Disposable implements IInputHandler { * ESC 8 * Restore cursor (ANSI.SYS). */ - public restoreCursor(params: number[]): void { + public restoreCursor(params?: IParams): void { this._terminal.buffer.x = this._terminal.buffer.savedX || 0; this._terminal.buffer.y = Math.max(this._terminal.buffer.savedY - this._terminal.buffer.ybase, 0); this._terminal.curAttrData.fg = this._terminal.buffer.savedCurAttrData.fg; diff --git a/src/Terminal.ts b/src/Terminal.ts index b28cfcfa..29a24500 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -56,6 +56,7 @@ import { Disposable } from 'common/Lifecycle'; import { IBufferSet, IBuffer } from 'common/buffer/Types'; import { Attributes } from 'common/buffer/Constants'; import { MouseService } from 'browser/services/MouseService'; +import { IParams } from 'common/parser/Types'; // Let it work inside Node.js for automated testing purposes. const document = (typeof window !== 'undefined') ? window.document : null; @@ -1413,7 +1414,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp } /** Add handler for CSI escape sequence. See xterm.d.ts for details. */ - public addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable { + public addCsiHandler(flag: string, callback: (params: IParams, collect: string) => boolean): IDisposable { return this._inputHandler.addCsiHandler(flag, callback); } /** Add handler for OSC escape sequence. See xterm.d.ts for details. */ diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index 07f91705..52887f19 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -15,6 +15,7 @@ import { AttributeData } from 'common/buffer/AttributeData'; import { IColorManager, IColorSet } from 'browser/Types'; import { IOptionsService } from 'common/services/Services'; import { EventEmitter } from 'common/EventEmitter'; +import { IParams } from 'common/parser/Types'; export class TestTerminal extends Terminal { writeSync(data: string): void { @@ -70,7 +71,7 @@ export class MockTerminal implements ITerminal { attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void { throw new Error('Method not implemented.'); } - addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable { + addCsiHandler(flag: string, callback: (params: IParams, collect: string) => boolean): IDisposable { throw new Error('Method not implemented.'); } addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable { diff --git a/src/Types.d.ts b/src/Types.d.ts index 5987d605..d5399b80 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -9,6 +9,7 @@ import { IEvent, IEventEmitter } from 'common/EventEmitter'; import { IColorSet } from 'browser/Types'; import { IOptionsService } from 'common/services/Services'; import { IBuffer, IBufferSet } from 'common/buffer/Types'; +import { IParams } from 'common/parser/Types'; export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean; @@ -112,42 +113,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[], 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[]): 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[]): 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[]): void; - /** CSI u */ restoreCursor(params?: number[]): void; + /** CSI @ */ insertChars(params?: IParams): void; + /** CSI A */ cursorUp(params?: IParams): void; + /** CSI B */ cursorDown(params?: IParams): void; + /** CSI C */ cursorForward(params?: IParams): void; + /** CSI D */ cursorBackward(params?: IParams): void; + /** CSI E */ cursorNextLine(params?: IParams): void; + /** CSI F */ cursorPrecedingLine(params?: IParams): void; + /** CSI G */ cursorCharAbsolute(params?: IParams): void; + /** CSI H */ cursorPosition(params?: IParams): void; + /** CSI I */ cursorForwardTab(params?: IParams): void; + /** CSI J */ eraseInDisplay(params?: IParams): void; + /** CSI K */ eraseInLine(params?: IParams): void; + /** CSI L */ insertLines(params?: IParams): void; + /** CSI M */ deleteLines(params?: IParams): void; + /** CSI P */ deleteChars(params?: IParams): void; + /** CSI S */ scrollUp(params?: IParams): void; + /** CSI T */ scrollDown(params?: IParams, collect?: string): void; + /** CSI X */ eraseChars(params?: IParams): void; + /** CSI Z */ cursorBackwardTab(params?: IParams): void; + /** CSI ` */ charPosAbsolute(params?: IParams): void; + /** CSI a */ hPositionRelative(params?: IParams): void; + /** CSI b */ repeatPrecedingCharacter(params?: IParams): void; + /** CSI c */ sendDeviceAttributes(params?: IParams, collect?: string): void; + /** CSI d */ linePosAbsolute(params?: IParams): void; + /** CSI e */ vPositionRelative(params?: IParams): void; + /** CSI f */ hVPosition(params?: IParams): void; + /** CSI g */ tabClear(params?: IParams): void; + /** CSI h */ setMode(params?: IParams, collect?: string): void; + /** CSI l */ resetMode(params?: IParams, collect?: string): void; + /** CSI m */ charAttributes(params?: IParams): void; + /** CSI n */ deviceStatus(params?: IParams, collect?: string): void; + /** CSI p */ softReset(params?: IParams, collect?: string): void; + /** CSI q */ setCursorStyle(params?: IParams, collect?: string): void; + /** CSI r */ setScrollRegion(params?: IParams, collect?: string): void; + /** CSI s */ saveCursor(params?: IParams): void; + /** CSI u */ restoreCursor(params?: IParams): void; /** OSC 0 OSC 2 */ setTitle(data: string): void; /** ESC E */ nextLine(): void; @@ -245,7 +246,7 @@ export interface IPublicTerminal extends IDisposable { writeln(data: string): void; open(parent: HTMLElement): void; attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void; - addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable; + addCsiHandler(flag: string, callback: (params: IParams, collect: string) => boolean): IDisposable; addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable; registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): number; deregisterLinkMatcher(matcherId: number): void; diff --git a/src/common/parser/EscapeSequenceParser.test.ts b/src/common/parser/EscapeSequenceParser.test.ts index f6e46a76..0777c921 100644 --- a/src/common/parser/EscapeSequenceParser.test.ts +++ b/src/common/parser/EscapeSequenceParser.test.ts @@ -69,14 +69,14 @@ const testTerminal: any = { 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]); + actionCSI: function (collect: string, params: IParams, flag: string): void { + this.calls.push(['csi', collect, params.toArray() as number[], 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]); + actionDCSHook: function (collect: string, params: IParams, flag: string): void { + this.calls.push(['dcs hook', collect, params.toArray() as number[], flag]); }, actionDCSPrint: function (data: Uint32Array, start: number, end: number): void { let s = ''; @@ -92,7 +92,7 @@ const 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 { + hook(collect: string, params: IParams, flag: number): void { testTerminal.actionDCSHook(collect, params, String.fromCharCode(flag)); } put(data: Uint32Array, start: number, end: number): void { @@ -124,7 +124,7 @@ let state: any; // parser with Uint8Array based transition table const testParser = new TestEscapeSequenceParser(); testParser.setPrintHandler(testTerminal.print.bind(testTerminal)); -testParser.setCsiHandlerFallback((collect: string, params: number[], flag: number) => { +testParser.setCsiHandlerFallback((collect: string, params: IParams, flag: number) => { testTerminal.actionCSI(collect, params, String.fromCharCode(flag)); }); testParser.setEscHandlerFallback((collect: string, flag: number) => { @@ -1146,8 +1146,8 @@ describe('EscapeSequenceParser', function (): void { chai.expect(esc).eql([]); }); it('CSI handler', function (): void { - parser2.setCsiHandler('m', function (params: number[], collect: string): void { - csi.push(['m', params, collect]); + parser2.setCsiHandler('m', function (params: IParams, collect: string): void { + csi.push(['m', params.toArray() as number[], collect]); }); parse(parser2, INPUT); chai.expect(csi).eql([['m', [1, 31], ''], ['m', [0], '']]); @@ -1160,16 +1160,16 @@ describe('EscapeSequenceParser', function (): void { describe('CSI custom handlers', () => { it('Prevent fallback', () => { const csiCustom: [string, number[], string][] = []; - parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params, collect])); - parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params, collect]); return true; }); + parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params.toArray() as number[], collect])); + parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params.toArray() as number[], collect]); return true; }); parse(parser2, INPUT); chai.expect(csi).eql([], 'Should not fallback to original handler'); chai.expect(csiCustom).eql([['m', [1, 31], ''], ['m', [0], '']]); }); it('Allow fallback', () => { const csiCustom: [string, number[], string][] = []; - parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params, collect])); - parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params, collect]); return false; }); + parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params.toArray() as number[], collect])); + parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params.toArray() as number[], collect]); return false; }); parse(parser2, INPUT); chai.expect(csi).eql([['m', [1, 31], ''], ['m', [0], '']], 'Should fallback to original handler'); chai.expect(csiCustom).eql([['m', [1, 31], ''], ['m', [0], '']]); @@ -1177,9 +1177,9 @@ describe('EscapeSequenceParser', function (): void { it('Multiple custom handlers fallback once', () => { const csiCustom: [string, number[], string][] = []; const csiCustom2: [string, number[], string][] = []; - parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params, collect])); - parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params, collect]); return true; }); - parser2.addCsiHandler('m', (params, collect) => { csiCustom2.push(['m', params, collect]); return false; }); + parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params.toArray() as number[], collect])); + parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params.toArray() as number[], collect]); return true; }); + parser2.addCsiHandler('m', (params, collect) => { csiCustom2.push(['m', params.toArray() as number[], collect]); return false; }); parse(parser2, INPUT); chai.expect(csi).eql([], 'Should not fallback to original handler'); chai.expect(csiCustom).eql([['m', [1, 31], ''], ['m', [0], '']]); @@ -1188,9 +1188,9 @@ describe('EscapeSequenceParser', function (): void { it('Multiple custom handlers no fallback', () => { const csiCustom: [string, number[], string][] = []; const csiCustom2: [string, number[], string][] = []; - parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params, collect])); - parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params, collect]); return true; }); - parser2.addCsiHandler('m', (params, collect) => { csiCustom2.push(['m', params, collect]); return true; }); + parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params.toArray() as number[], collect])); + parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params.toArray() as number[], collect]); return true; }); + parser2.addCsiHandler('m', (params, collect) => { csiCustom2.push(['m', params.toArray() as number[], collect]); return true; }); parse(parser2, INPUT); chai.expect(csi).eql([], 'Should not fallback to original handler'); chai.expect(csiCustom).eql([], 'Should not fallback once'); @@ -1206,8 +1206,8 @@ describe('EscapeSequenceParser', function (): void { }); it('Dispose should work', () => { const csiCustom: [string, number[], string][] = []; - parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params, collect])); - const customHandler = parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params, collect]); return true; }); + parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params.toArray() as number[], collect])); + const customHandler = parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params.toArray() as number[], collect]); return true; }); customHandler.dispose(); parse(parser2, INPUT); chai.expect(csi).eql([['m', [1, 31], ''], ['m', [0], '']]); @@ -1215,8 +1215,8 @@ describe('EscapeSequenceParser', function (): void { }); it('Should not corrupt the parser when dispose is called twice', () => { const csiCustom: [string, number[], string][] = []; - parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params, collect])); - const customHandler = parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params, collect]); return true; }); + parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params.toArray() as number[], collect])); + const customHandler = parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params.toArray() as number[], collect]); return true; }); customHandler.dispose(); customHandler.dispose(); parse(parser2, INPUT); @@ -1320,8 +1320,8 @@ describe('EscapeSequenceParser', function (): void { }); it('DCS handler', function (): void { parser2.setDcsHandler('+p', { - hook: function (collect: string, params: number[], flag: number): void { - dcs.push(['hook', collect, params, flag]); + hook: function (collect: string, params: IParams, flag: number): void { + dcs.push(['hook', collect, params.toArray() as number[], flag]); }, put: function (data: Uint32Array, start: number, end: number): void { let s = ''; @@ -1361,7 +1361,7 @@ describe('EscapeSequenceParser', function (): void { currentState: ParserState.CSI_PARAM, osc: '', collect: '', - params: [1, 2, 0], // extra zero here + params: Params.fromArray([1, 2, 0]), // extra zero here abort: false }); parser2.clearErrorHandler(); diff --git a/src/common/parser/EscapeSequenceParser.ts b/src/common/parser/EscapeSequenceParser.ts index 843eb4c9..81b70d23 100644 --- a/src/common/parser/EscapeSequenceParser.ts +++ b/src/common/parser/EscapeSequenceParser.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IParsingState, IDcsHandler, IEscapeSequenceParser } from 'common/parser/Types'; +import { IParsingState, IDcsHandler, IEscapeSequenceParser, IParams } from 'common/parser/Types'; import { ParserState, ParserAction } from 'common/parser/Constants'; import { Disposable } from 'common/Lifecycle'; import { utf32ToString } from 'common/input/TextDecoder'; @@ -15,7 +15,7 @@ interface IHandlerCollection { [key: string]: T[]; } -type CsiHandler = (params: number[], collect: string) => boolean | void; +type CsiHandler = (params: IParams, collect: string) => boolean | void; type OscHandler = (data: string) => boolean | void; /** @@ -207,7 +207,7 @@ export const VT500_TRANSITION_TABLE = (function (): TransitionTable { * Dummy DCS handler as default fallback. */ class DcsDummy implements IDcsHandler { - hook(collect: string, params: number[], flag: number): void { } + hook(collect: string, params: IParams, flag: number): void { } put(data: Uint32Array, start: number, end: number): void { } unhook(): void { } } @@ -247,7 +247,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP // fallback handlers protected _printHandlerFb: (data: Uint32Array, start: number, end: number) => void; protected _executeHandlerFb: (code: number) => void; - protected _csiHandlerFb: (collect: string, params: number[], flag: number) => void; + protected _csiHandlerFb: (collect: string, params: IParams, flag: number) => void; protected _escHandlerFb: (collect: string, flag: number) => void; protected _oscHandlerFb: (identifier: number, data: string) => void; protected _dcsHandlerFb: IDcsHandler; @@ -267,7 +267,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP // set default fallback handlers and handler lookup containers this._printHandlerFb = (data, start, end): void => { }; this._executeHandlerFb = (code: number): void => { }; - this._csiHandlerFb = (collect: string, params: number[], flag: number): void => { }; + this._csiHandlerFb = (collect: string, params: IParams, flag: number): void => { }; this._escHandlerFb = (collect: string, flag: number): void => { }; this._oscHandlerFb = (identifier: number, data: string): void => { }; this._dcsHandlerFb = new DcsDummy(); @@ -325,13 +325,13 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP } }; } - setCsiHandler(flag: string, callback: (params: number[], collect: string) => void): void { + setCsiHandler(flag: string, callback: (params: IParams, collect: string) => void): 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: (collect: string, params: number[], flag: number) => void): void { + setCsiHandlerFallback(callback: (collect: string, params: IParams, flag: number) => void): void { this._csiHandlerFb = callback; } @@ -469,7 +469,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP currentState, osc, collect, - params: params.toArray() as number[], + params: params, abort: false }); if (inject.abort) return; @@ -485,12 +485,12 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP let j = handlers ? handlers.length - 1 : -1; for (; j >= 0; j--) { // undefined or true means success and to stop bubbling - if (handlers[j](params.toArray() as number[], collect) !== false) { + if (handlers[j](params, collect) !== false) { break; } } if (j < 0) { - this._csiHandlerFb(collect, params.toArray() as number[], code); + this._csiHandlerFb(collect, params, code); } break; case ParserAction.PARAM: @@ -532,7 +532,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP this.precedingCodepoint = 0; dcsHandler = this._dcsHandlers[collect + String.fromCharCode(code)]; if (!dcsHandler) dcsHandler = this._dcsHandlerFb; - dcsHandler.hook(collect, params.toArray() as number[], code); + dcsHandler.hook(collect, params, code); break; case ParserAction.DCS_PUT: // inner loop - exit DCS_PUT: 0x18, 0x1a, 0x1b, 0x7f, 0x80 - 0x9f diff --git a/src/common/parser/Types.d.ts b/src/common/parser/Types.d.ts index ffd175d6..17f57377 100644 --- a/src/common/parser/Types.d.ts +++ b/src/common/parser/Types.d.ts @@ -60,7 +60,7 @@ export interface IParsingState { // collect buffer with intermediate characters collect: string; // params buffer - params: number[]; + params: IParams; // should abort (default: false) abort: boolean; } @@ -87,7 +87,7 @@ export interface IParsingState { * `unhook` marks the end of the current DCS sequence. */ export interface IDcsHandler { - hook(collect: string, params: number[], flag: number): void; + hook(collect: string, params: IParams, flag: number): void; put(data: Uint32Array, start: number, end: number): void; unhook(): void; } @@ -120,10 +120,10 @@ export interface IEscapeSequenceParser extends IDisposable { clearExecuteHandler(flag: string): void; setExecuteHandlerFallback(callback: (code: number) => void): void; - setCsiHandler(flag: string, callback: (params: number[], collect: string) => void): void; + setCsiHandler(flag: string, callback: (params: IParams, collect: string) => void): void; clearCsiHandler(flag: string): void; - setCsiHandlerFallback(callback: (collect: string, params: number[], flag: number) => void): void; - addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable; + setCsiHandlerFallback(callback: (collect: string, params: IParams, flag: number) => void): void; + addCsiHandler(flag: string, callback: (params: IParams, collect: string) => boolean): IDisposable; addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable; setEscHandler(collectAndFlag: string, callback: () => void): void; diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index 0dae4def..70486207 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi } from 'xterm'; +import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi, IParams } from 'xterm'; import { ITerminal } from '../Types'; import { IBufferLine } from 'common/Types'; import { IBuffer } from 'common/buffer/Types'; @@ -55,7 +55,7 @@ export class Terminal implements ITerminalApi { public attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void { this._core.attachCustomKeyEventHandler(customKeyEventHandler); } - public addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable { + public addCsiHandler(flag: string, callback: (params: IParams, collect: string) => boolean): IDisposable { return this._core.addCsiHandler(flag, callback); } public addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable { diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index f435367d..d2c16e1d 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -493,7 +493,7 @@ declare module 'xterm' { * The most recently-added handler is tried first. * @return An IDisposable you can call to remove this handler. */ - addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable; + addCsiHandler(flag: string, callback: (params: IParams, collect: string) => boolean): IDisposable; /** * (EXPERIMENTAL) Adds a handler for OSC escape sequences. @@ -925,4 +925,28 @@ declare module 'xterm' { */ readonly width: number; } + + interface IParams { + /** from ctor */ + maxLength: number; + maxSubParamsLength: number; + + /** param values and its length */ + params: Int16Array; + length: number; + + /** sub params and its length */ + subParams: Int16Array; + subParamsLength: number; + + /** methods */ + clone(): IParams; + toArray(): (number | number[])[]; + reset(): void; + addParam(value: number): void; + addSubParam(value: number): void; + hasSubParams(idx: number): boolean; + getSubParams(idx: number): Int16Array | null; + getSubParamsAll(): {[idx: number]: Int16Array}; + } } From 62007c3be4bf6cce229f8e4c461a9aa23dadb4c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 17 Jun 2019 23:42:56 +0200 Subject: [PATCH 06/25] comment storable amount of params in parser --- src/common/parser/EscapeSequenceParser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/parser/EscapeSequenceParser.ts b/src/common/parser/EscapeSequenceParser.ts index 81b70d23..dcd99e97 100644 --- a/src/common/parser/EscapeSequenceParser.ts +++ b/src/common/parser/EscapeSequenceParser.ts @@ -259,7 +259,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP this.initialState = ParserState.GROUND; this.currentState = this.initialState; this._osc = ''; - this._params = new Params(); + this._params = new Params(); // defaults to 32 storable params/subparams this._params.addParam(0); this._collect = ''; this.precedingCodepoint = 0; From c558d485e00ac5bc97f9f002aacb4792bf6e9305 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 18 Jun 2019 03:00:53 +0200 Subject: [PATCH 07/25] apply colon handling to SGR --- src/InputHandler.ts | 96 ++++++++++++++++++++++++++++++++------------- 1 file changed, 68 insertions(+), 28 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index b5c83293..e831b4f4 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -19,6 +19,7 @@ import { IParsingState, IDcsHandler, IEscapeSequenceParser, IParams } from 'comm import { NULL_CELL_CODE, NULL_CELL_WIDTH, Attributes, FgFlags, BgFlags } from 'common/buffer/Constants'; import { CellData } from 'common/buffer/CellData'; import { AttributeData } from 'common/buffer/AttributeData'; +import { IAttributeData } from 'common/Types'; /** * Map collect to glevel. Used in `selectCharset`. @@ -1535,6 +1536,71 @@ export class InputHandler extends Disposable implements IInputHandler { } } + /** + * Helper to extract and apply color params/subparams. + * Returns advance for params index. + */ + private _extractColor(params: IParams, pos: number, attr: IAttributeData): number { + // normalize params + // meaning: [target, CM, ign, val, val, val] + // RGB : [ 38/48, 2, ign, r, g, b] + // P256 : [ 38/48, 5, ign, v, ign, ign] + const accu = [0, 0, -1, 0, 0, 0]; + let cSpace = 0; + let advance = 0; + + do { + accu[advance + cSpace] = params.params[pos + advance]; + if (params.hasSubParams(pos + advance)) { + const subparams = params.getSubParams(pos + advance); + let i = 0; + do { + accu[advance + i + 1 + cSpace] = subparams[i]; + } while (++i < subparams.length && i + advance + 1 + cSpace < accu.length); + break; + } + // exit early if can decide color mode with semicolons + if ((accu[1] === 5 && advance + cSpace >= 2) + || (accu[1] === 2 && advance + cSpace >= 5)) { + break; + } + // offset colorSpace slot for semicolon mode + if (accu[1]) { + cSpace = 1; + } + } while (++advance + pos < params.length && advance + cSpace < accu.length); + + // set default values to 0 + for (let i = 2; i < accu.length; ++i) { + if (accu[i] === -1) { + accu[i] = 0; + } + } + + // apply colors + if (accu[0] === 38) { + if (accu[1] === 2) { + attr.fg |= Attributes.CM_RGB; + attr.fg &= ~Attributes.RGB_MASK; + attr.fg |= AttributeData.fromColorRGB([accu[3], accu[4], accu[5]]); + } else if (accu[1] === 5) { + attr.fg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK); + attr.fg |= Attributes.CM_P256 | (accu[3] & 0xff); + } + } else if (accu[0] === 48) { + if (accu[1] === 2) { + attr.bg |= Attributes.CM_RGB; + attr.bg &= ~Attributes.RGB_MASK; + attr.bg |= AttributeData.fromColorRGB([accu[3], accu[4], accu[5]]); + } else if (accu[1] === 5) { + attr.bg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK); + attr.bg |= Attributes.CM_P256 | (accu[3] & 0xff); + } + } + + return advance; + } + /** * CSI Pm m Character Attributes (SGR). * Ps = 0 -> Normal (default). @@ -1683,35 +1749,9 @@ export class InputHandler extends Disposable implements IInputHandler { // reset bg attr.bg &= ~(Attributes.CM_MASK | Attributes.RGB_MASK); attr.bg |= DEFAULT_ATTR_DATA.bg & (Attributes.PCOLOR_MASK | Attributes.RGB_MASK); - } else if (p === 38) { + } else if (p === 38 || p === 48) { // fg color 256 and RGB - // FIXME: apply sub parameter, avoid reading over length! - if (params.params[i + 1] === 2) { - i += 2; - attr.fg |= Attributes.CM_RGB; - attr.fg &= ~Attributes.RGB_MASK; - attr.fg |= AttributeData.fromColorRGB([params.params[i], params.params[i + 1], params.params[i + 2]]); - i += 2; - } else if (params.params[i + 1] === 5) { - i += 2; - p = params.params[i] & 0xff; - attr.fg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK); - attr.fg |= Attributes.CM_P256 | p; - } - } else if (p === 48) { - // bg color 256 and RGB - if (params.params[i + 1] === 2) { - i += 2; - attr.bg |= Attributes.CM_RGB; - attr.bg &= ~Attributes.RGB_MASK; - attr.bg |= AttributeData.fromColorRGB([params.params[i], params.params[i + 1], params.params[i + 2]]); - i += 2; - } else if (params.params[i + 1] === 5) { - i += 2; - p = params.params[i] & 0xff; - attr.bg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK); - attr.bg |= Attributes.CM_P256 | p; - } + i += this._extractColor(params, i, attr); } else if (p === 100) { // reset fg/bg attr.fg &= ~(Attributes.CM_MASK | Attributes.RGB_MASK); From 6f047aa7ea900539bb278db5d2ce6ac2a41996c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 18 Jun 2019 14:40:15 +0200 Subject: [PATCH 08/25] SGR tests, fix bug in 38:5 handling --- src/InputHandler.test.ts | 150 +++++++++++++++++++++++++++++++++++++++ src/InputHandler.ts | 7 ++ 2 files changed, 157 insertions(+) diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index 73365af8..ef9bdde4 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -546,4 +546,154 @@ describe('InputHandler', () => { assert.deepEqual(AttributeData.toColorRGB(term.curAttrData.getFgColor()), [5, 0, 0]); }); }); + describe('colon notation', () => { + let termColon: TestTerminal; + let termSemicolon: TestTerminal; + beforeEach(() => { + termColon = new TestTerminal(); + termSemicolon = new TestTerminal(); + }); + describe('should equal to semicolon', () => { + it('CSI 38:2::50:100:150 m', () => { + termColon.curAttrData.fg = 0xFFFFFFFF; + termSemicolon.curAttrData.fg = 0xFFFFFFFF; + termSemicolon.writeSync('\x1b[38;2;50;100;150m'); + termColon.writeSync('\x1b[38:2::50:100:150m'); + assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150); + assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); + }); + it('CSI 38:2::50:100: m', () => { + termColon.curAttrData.fg = 0xFFFFFFFF; + termSemicolon.curAttrData.fg = 0xFFFFFFFF; + termSemicolon.writeSync('\x1b[38;2;50;100;m'); + termColon.writeSync('\x1b[38:2::50:100:m'); + assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 0); + assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); + }); + it('CSI 38:2::50:: m', () => { + termColon.curAttrData.fg = 0xFFFFFFFF; + termSemicolon.curAttrData.fg = 0xFFFFFFFF; + termSemicolon.writeSync('\x1b[38;2;50;;m'); + termColon.writeSync('\x1b[38:2::50::m'); + assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 50 << 16 | 0 << 8 | 0); + assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); + }); + it('CSI 38:2:::: m', () => { + termColon.curAttrData.fg = 0xFFFFFFFF; + termSemicolon.curAttrData.fg = 0xFFFFFFFF; + termSemicolon.writeSync('\x1b[38;2;;;m'); + termColon.writeSync('\x1b[38:2::::m'); + assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 0 << 16 | 0 << 8 | 0); + assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); + }); + it('CSI 38;2::50:100:150 m', () => { + termColon.curAttrData.fg = 0xFFFFFFFF; + termSemicolon.curAttrData.fg = 0xFFFFFFFF; + termSemicolon.writeSync('\x1b[38;2;50;100;150m'); + termColon.writeSync('\x1b[38;2::50:100:150m'); + assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150); + assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); + }); + it('CSI 38;2;50:100:150 m', () => { + termColon.curAttrData.fg = 0xFFFFFFFF; + termSemicolon.curAttrData.fg = 0xFFFFFFFF; + termSemicolon.writeSync('\x1b[38;2;50;100;150m'); + termColon.writeSync('\x1b[38;2;50:100:150m'); + assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150); + assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); + }); + it('CSI 38;2;50;100:150 m', () => { + termColon.curAttrData.fg = 0xFFFFFFFF; + termSemicolon.curAttrData.fg = 0xFFFFFFFF; + termSemicolon.writeSync('\x1b[38;2;50;100;150m'); + termColon.writeSync('\x1b[38;2;50;100:150m'); + assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150); + assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); + }); + it('CSI 38:5:50 m', () => { + termColon.curAttrData.fg = 0xFFFFFFFF; + termSemicolon.curAttrData.fg = 0xFFFFFFFF; + termSemicolon.writeSync('\x1b[38;5;50m'); + termColon.writeSync('\x1b[38:5:50m'); + assert.equal(termSemicolon.curAttrData.fg & 0xFF, 50); + assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); + }); + it('CSI 38:5: m', () => { + termColon.curAttrData.fg = 0xFFFFFFFF; + termSemicolon.curAttrData.fg = 0xFFFFFFFF; + termSemicolon.writeSync('\x1b[38;5;m'); + termColon.writeSync('\x1b[38:5:m'); + assert.equal(termSemicolon.curAttrData.fg & 0xFF, 0); + assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); + }); + it('CSI 38;5:50 m', () => { + termColon.curAttrData.fg = 0xFFFFFFFF; + termSemicolon.curAttrData.fg = 0xFFFFFFFF; + termSemicolon.writeSync('\x1b[38;5;50m'); + termColon.writeSync('\x1b[38;5:50m'); + assert.equal(termSemicolon.curAttrData.fg & 0xFF, 50); + assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); + }); + }); + describe('should fill early sequence end with default of 0', () => { + it('CSI 38:2 m', () => { + termColon.curAttrData.fg = 0xFFFFFFFF; + termSemicolon.curAttrData.fg = 0xFFFFFFFF; + termSemicolon.writeSync('\x1b[38;2m'); + termColon.writeSync('\x1b[38:2m'); + assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 0 << 16 | 0 << 8 | 0); + assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); + }); + it('CSI 38:5 m', () => { + termColon.curAttrData.fg = 0xFFFFFFFF; + termSemicolon.curAttrData.fg = 0xFFFFFFFF; + termSemicolon.writeSync('\x1b[38;5m'); + termColon.writeSync('\x1b[38:5m'); + assert.equal(termSemicolon.curAttrData.fg & 0xFF, 0); + assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); + }); + }); + describe('should not interfere with leading/following SGR attrs', () => { + it('CSI 1 ; 38:2::50:100:150 ; 4 m', () => { + termSemicolon.writeSync('\x1b[1;38;2;50;100;150;4m'); + termColon.writeSync('\x1b[1;38:2::50:100:150;4m'); + assert.equal(!!termSemicolon.curAttrData.isBold(), true); + assert.equal(!!termSemicolon.curAttrData.isUnderline(), true); + assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150); + assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); + }); + it('CSI 1 ; 38:2::50:100: ; 4 m', () => { + termSemicolon.writeSync('\x1b[1;38;2;50;100;;4m'); + termColon.writeSync('\x1b[1;38:2::50:100:;4m'); + assert.equal(!!termSemicolon.curAttrData.isBold(), true); + assert.equal(!!termSemicolon.curAttrData.isUnderline(), true); + assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 0); + assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); + }); + it('CSI 1 ; 38:2::50:100 ; 4 m', () => { + termSemicolon.writeSync('\x1b[1;38;2;50;100;;4m'); + termColon.writeSync('\x1b[1;38:2::50:100;4m'); + assert.equal(!!termSemicolon.curAttrData.isBold(), true); + assert.equal(!!termSemicolon.curAttrData.isUnderline(), true); + assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 0); + assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); + }); + it('CSI 1 ; 38:2:: ; 4 m', () => { + termSemicolon.writeSync('\x1b[1;38;2;;;;4m'); + termColon.writeSync('\x1b[1;38:2::;4m'); + assert.equal(!!termSemicolon.curAttrData.isBold(), true); + assert.equal(!!termSemicolon.curAttrData.isUnderline(), true); + assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 0); + assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); + }); + it('CSI 1 ; 38;2:: ; 4 m', () => { + termSemicolon.writeSync('\x1b[1;38;2;;;;4m'); + termColon.writeSync('\x1b[1;38;2::;4m'); + assert.equal(!!termSemicolon.curAttrData.isBold(), true); + assert.equal(!!termSemicolon.curAttrData.isUnderline(), true); + assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 0); + assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); + }); + }); + }); }); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index e831b4f4..7698c30e 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -1546,7 +1546,11 @@ export class InputHandler extends Disposable implements IInputHandler { // RGB : [ 38/48, 2, ign, r, g, b] // P256 : [ 38/48, 5, ign, v, ign, ign] const accu = [0, 0, -1, 0, 0, 0]; + + // alignment placeholder for non color space sequences let cSpace = 0; + + // return advance we took in params let advance = 0; do { @@ -1555,6 +1559,9 @@ export class InputHandler extends Disposable implements IInputHandler { const subparams = params.getSubParams(pos + advance); let i = 0; do { + if (accu[1] === 5) { + cSpace = 1; + } accu[advance + i + 1 + cSpace] = subparams[i]; } while (++i < subparams.length && i + advance + 1 + cSpace < accu.length); break; From 069e51064ab830a9c3f2b941858c95cb20464660 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 18 Jun 2019 14:47:35 +0200 Subject: [PATCH 09/25] remove as number[] type conversions from tests --- .../parser/EscapeSequenceParser.test.ts | 64 ++++++++++--------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/src/common/parser/EscapeSequenceParser.test.ts b/src/common/parser/EscapeSequenceParser.test.ts index 0777c921..9d0b2f77 100644 --- a/src/common/parser/EscapeSequenceParser.test.ts +++ b/src/common/parser/EscapeSequenceParser.test.ts @@ -10,6 +10,8 @@ import { StringToUtf32, stringFromCodePoint } from 'common/input/TextDecoder'; import { ParserState } from 'common/parser/Constants'; import { Params } from 'common/parser/Params'; +type ParamsArray = (number | number[])[]; + function r(a: number, b: number): string[] { let c = b - a; const arr = new Array(c); @@ -27,10 +29,10 @@ class TestEscapeSequenceParser extends EscapeSequenceParser { public set osc(value: string) { this._osc = value; } - public get params(): number[] { - return this._params.toArray() as number[]; + public get params(): ParamsArray { + return this._params.toArray() as ParamsArray; } - public set params(value: number[]) { + public set params(value: ParamsArray) { this._params = Params.fromArray(value); } public get realParams(): IParams { @@ -70,13 +72,13 @@ const testTerminal: any = { this.calls.push(['exe', flag]); }, actionCSI: function (collect: string, params: IParams, flag: string): void { - this.calls.push(['csi', collect, params.toArray() as number[], flag]); + this.calls.push(['csi', collect, params.toArray(), flag]); }, actionESC: function (collect: string, flag: string): void { this.calls.push(['esc', collect, flag]); }, actionDCSHook: function (collect: string, params: IParams, flag: string): void { - this.calls.push(['dcs hook', collect, params.toArray() as number[], flag]); + this.calls.push(['dcs hook', collect, params.toArray(), flag]); }, actionDCSPrint: function (data: Uint32Array, start: number, end: number): void { let s = ''; @@ -1096,10 +1098,10 @@ describe('EscapeSequenceParser', function (): void { let parser2: TestEscapeSequenceParser; let print = ''; const esc: string[] = []; - const csi: [string, number[], string][] = []; + const csi: [string, ParamsArray, string][] = []; const exe: string[] = []; const osc: [number, string][] = []; - const dcs: ([string] | [string, string] | [string, string, number[], number])[] = []; + const dcs: ([string] | [string, string] | [string, string, ParamsArray, number])[] = []; function clearAccu(): void { print = ''; esc.length = 0; @@ -1147,7 +1149,7 @@ describe('EscapeSequenceParser', function (): void { }); it('CSI handler', function (): void { parser2.setCsiHandler('m', function (params: IParams, collect: string): void { - csi.push(['m', params.toArray() as number[], collect]); + csi.push(['m', params.toArray(), collect]); }); parse(parser2, INPUT); chai.expect(csi).eql([['m', [1, 31], ''], ['m', [0], '']]); @@ -1159,38 +1161,38 @@ describe('EscapeSequenceParser', function (): void { }); describe('CSI custom handlers', () => { it('Prevent fallback', () => { - const csiCustom: [string, number[], string][] = []; - parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params.toArray() as number[], collect])); - parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params.toArray() as number[], collect]); return true; }); + const csiCustom: [string, ParamsArray, string][] = []; + parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params.toArray(), collect])); + parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params.toArray(), collect]); return true; }); parse(parser2, INPUT); chai.expect(csi).eql([], 'Should not fallback to original handler'); chai.expect(csiCustom).eql([['m', [1, 31], ''], ['m', [0], '']]); }); it('Allow fallback', () => { - const csiCustom: [string, number[], string][] = []; - parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params.toArray() as number[], collect])); - parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params.toArray() as number[], collect]); return false; }); + const csiCustom: [string, ParamsArray, string][] = []; + parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params.toArray(), collect])); + parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params.toArray(), collect]); return false; }); parse(parser2, INPUT); chai.expect(csi).eql([['m', [1, 31], ''], ['m', [0], '']], 'Should fallback to original handler'); chai.expect(csiCustom).eql([['m', [1, 31], ''], ['m', [0], '']]); }); it('Multiple custom handlers fallback once', () => { - const csiCustom: [string, number[], string][] = []; - const csiCustom2: [string, number[], string][] = []; - parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params.toArray() as number[], collect])); - parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params.toArray() as number[], collect]); return true; }); - parser2.addCsiHandler('m', (params, collect) => { csiCustom2.push(['m', params.toArray() as number[], collect]); return false; }); + const csiCustom: [string, ParamsArray, string][] = []; + const csiCustom2: [string, ParamsArray, string][] = []; + parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params.toArray(), collect])); + parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params.toArray(), collect]); return true; }); + parser2.addCsiHandler('m', (params, collect) => { csiCustom2.push(['m', params.toArray(), collect]); return false; }); parse(parser2, INPUT); chai.expect(csi).eql([], 'Should not fallback to original handler'); chai.expect(csiCustom).eql([['m', [1, 31], ''], ['m', [0], '']]); chai.expect(csiCustom2).eql([['m', [1, 31], ''], ['m', [0], '']]); }); it('Multiple custom handlers no fallback', () => { - const csiCustom: [string, number[], string][] = []; - const csiCustom2: [string, number[], string][] = []; - parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params.toArray() as number[], collect])); - parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params.toArray() as number[], collect]); return true; }); - parser2.addCsiHandler('m', (params, collect) => { csiCustom2.push(['m', params.toArray() as number[], collect]); return true; }); + const csiCustom: [string, ParamsArray, string][] = []; + const csiCustom2: [string, ParamsArray, string][] = []; + parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params.toArray(), collect])); + parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params.toArray(), collect]); return true; }); + parser2.addCsiHandler('m', (params, collect) => { csiCustom2.push(['m', params.toArray(), collect]); return true; }); parse(parser2, INPUT); chai.expect(csi).eql([], 'Should not fallback to original handler'); chai.expect(csiCustom).eql([], 'Should not fallback once'); @@ -1205,18 +1207,18 @@ describe('EscapeSequenceParser', function (): void { chai.expect(order).eql([3, 2, 1]); }); it('Dispose should work', () => { - const csiCustom: [string, number[], string][] = []; - parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params.toArray() as number[], collect])); - const customHandler = parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params.toArray() as number[], collect]); return true; }); + const csiCustom: [string, ParamsArray, string][] = []; + parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params.toArray(), collect])); + const customHandler = parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params.toArray(), collect]); return true; }); customHandler.dispose(); parse(parser2, INPUT); chai.expect(csi).eql([['m', [1, 31], ''], ['m', [0], '']]); chai.expect(csiCustom).eql([], 'Should not use custom handler as it was disposed'); }); it('Should not corrupt the parser when dispose is called twice', () => { - const csiCustom: [string, number[], string][] = []; - parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params.toArray() as number[], collect])); - const customHandler = parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params.toArray() as number[], collect]); return true; }); + const csiCustom: [string, ParamsArray, string][] = []; + parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params.toArray(), collect])); + const customHandler = parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params.toArray(), collect]); return true; }); customHandler.dispose(); customHandler.dispose(); parse(parser2, INPUT); @@ -1321,7 +1323,7 @@ describe('EscapeSequenceParser', function (): void { it('DCS handler', function (): void { parser2.setDcsHandler('+p', { hook: function (collect: string, params: IParams, flag: number): void { - dcs.push(['hook', collect, params.toArray() as number[], flag]); + dcs.push(['hook', collect, params.toArray(), flag]); }, put: function (data: Uint32Array, start: number, end: number): void { let s = ''; From f9842045706fe7718befb1447ac9d41d4d8a82ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 18 Jun 2019 14:56:32 +0200 Subject: [PATCH 10/25] parser tests --- src/common/parser/EscapeSequenceParser.test.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/common/parser/EscapeSequenceParser.test.ts b/src/common/parser/EscapeSequenceParser.test.ts index 9d0b2f77..6a1e20ef 100644 --- a/src/common/parser/EscapeSequenceParser.test.ts +++ b/src/common/parser/EscapeSequenceParser.test.ts @@ -1048,6 +1048,23 @@ describe('EscapeSequenceParser', function (): void { ['print', 'defg'] ], null); }); + it('colon notation in CSI params', () => { + test('\x1b[<31;5::123:;8mHello World! öäü€\nabc', + [ + ['csi', '<', [31, 5, [-1, 123, -1], 8], 'm'], + ['print', 'Hello World! öäü€'], + ['exe', '\n'], + ['print', 'abc'] + ], null); + }); + it('colon notation in DCS params', function (): void { + test('abc\x901;2::55;3+$abc;de\x9c', [ + ['print', 'abc'], + ['dcs hook', '+$', [1, 2, [-1, 55], 3], 'a'], + ['dcs put', 'bc;de'], + ['dcs unhook'] + ], null); + }); }); describe('coverage tests', function (): void { From fc778c76c2a6264e5ac9a3bcf33dce2375ee26b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 18 Jun 2019 15:23:36 +0200 Subject: [PATCH 11/25] add usage example for action handler --- src/common/parser/Params.ts | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/common/parser/Params.ts b/src/common/parser/Params.ts index 74cd5a79..27b180da 100644 --- a/src/common/parser/Params.ts +++ b/src/common/parser/Params.ts @@ -8,9 +8,31 @@ import { IParams } from 'common/parser/Types'; * Params storage class. * This type is used by the parser to acuumulate sequence parameters and sub parameters * and transmit them to the input handler actions. - * Note: The params object for the handler actions is borrowed from the parser - * and will be lost after the handler exits. Use either `toArray` or `clone` to get - * a stable copy of the data. + * + * Usage in action handler: + * ```typescript + * function handler(params: IParams): void { + * for (let i = 0; i < params.length; ++i) { + * // get single param + * const param = params.params[i]; + * ... + * // check for sub params + * if (Params.hasSubParams(i)) { + * // get sub params + * const subparams = params.getSubParams(i); + * ... + * } + * } + * } + * ``` + * + * NOTES: + * - params object for action handlers is borrowed, use `.toArray` or `.clone` to get a copy + * - never read beyond `params.length - 1` (likely to contain arbitrary data) + * - `.getSubParams` returns a borrowed typed array, use `.getSubParamsAll` for cloned sub params + * - hardcoded limitations: + * - max. value for a single (sub) param is 2^15 (caveat: will overflow to negative values) + * - max. 256 sub params possible */ export class Params implements IParams { // params store and length From c80ff1e1d1cae5299fd8a3095607a9b48cbe9291 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 18 Jun 2019 15:37:53 +0200 Subject: [PATCH 12/25] cleanup interface --- src/common/parser/Params.test.ts | 19 ++++++++++++----- src/common/parser/Params.ts | 36 ++++++++++++++++---------------- src/common/parser/Types.d.ts | 4 ---- typings/xterm.d.ts | 17 +++++---------- 4 files changed, 37 insertions(+), 39 deletions(-) diff --git a/src/common/parser/Params.test.ts b/src/common/parser/Params.test.ts index 4b45ba3f..dca5f1c2 100644 --- a/src/common/parser/Params.test.ts +++ b/src/common/parser/Params.test.ts @@ -5,6 +5,15 @@ import { assert } from 'chai'; import { Params } from 'common/parser/Params'; +class TestParams extends Params { + public get subParams(): Int16Array { + return this._subParams; + } + public get subParamsLength(): number { + return this._subParamsLength; + } +} + /** `Params` parser shim */ function parse(params: Params, s: string): void { params.reset(); @@ -33,13 +42,13 @@ function parse(params: Params, s: string): void { describe('Params', () => { it('should respect ctor args', () => { - const params = new Params(12, 23); + const params = new TestParams(12, 23); assert.equal(params.params.length, 12); assert.equal(params.subParams.length, 23); assert.deepEqual(params.toArray(), []); }); it('addParam', () => { - const params = new Params(); + const params = new TestParams(); params.addParam(1); assert.equal(params.length, 1); assert.deepEqual(Array.prototype.slice.call(params.params, 0, params.length), [1]); @@ -51,7 +60,7 @@ describe('Params', () => { assert.equal(params.subParamsLength, 0); }); it('addSubParam', () => { - const params = new Params(); + const params = new TestParams(); params.addParam(1); params.addSubParam(2); params.addSubParam(3); @@ -65,7 +74,7 @@ describe('Params', () => { assert.deepEqual(params.toArray(), [1, [2, 3], 12345, [-1]]); }); it('should not add sub params without previous param', () => { - const params = new Params(); + const params = new TestParams(); params.addSubParam(2); params.addSubParam(3); assert.equal(params.length, 0); @@ -79,7 +88,7 @@ describe('Params', () => { assert.deepEqual(params.toArray(), [1, [2, 3]]); }); it('reset', () => { - const params = new Params(); + const params = new TestParams(); params.addParam(1); params.addSubParam(2); params.addSubParam(3); diff --git a/src/common/parser/Params.ts b/src/common/parser/Params.ts index 27b180da..16b9d74e 100644 --- a/src/common/parser/Params.ts +++ b/src/common/parser/Params.ts @@ -34,14 +34,14 @@ import { IParams } from 'common/parser/Types'; * - max. value for a single (sub) param is 2^15 (caveat: will overflow to negative values) * - max. 256 sub params possible */ -export class Params implements IParams { +export class Params { // params store and length public params: Int16Array; public length: number; // sub params store and length - public subParams: Int16Array; - public subParamsLength: number; + protected _subParams: Int16Array; + protected _subParamsLength: number; // sub params offsets from param: param idx --> [start, end] offset private _subParamsIdx: Uint16Array; @@ -81,8 +81,8 @@ export class Params implements IParams { } this.params = new Int16Array(maxLength); this.length = 0; - this.subParams = new Int16Array(maxSubParamsLength); - this.subParamsLength = 0; + this._subParams = new Int16Array(maxSubParamsLength); + this._subParamsLength = 0; this._subParamsIdx = new Uint16Array(maxLength); this._rejectDigits = false; this._rejectSubDigits = false; @@ -95,8 +95,8 @@ export class Params implements IParams { const newParams = new Params(this.maxLength, this.maxSubParamsLength); newParams.params.set(this.params); newParams.length = this.length; - newParams.subParams.set(this.subParams); - newParams.subParamsLength = this.subParamsLength; + newParams._subParams.set(this._subParams); + newParams._subParamsLength = this._subParamsLength; newParams._subParamsIdx.set(this._subParamsIdx); return newParams; } @@ -114,7 +114,7 @@ export class Params implements IParams { const start = this._subParamsIdx[i] >> 8; const end = this._subParamsIdx[i] & 0xFF; if (end - start > 0) { - res.push(Array.prototype.slice.call(this.subParams, start, end)); + res.push(Array.prototype.slice.call(this._subParams, start, end)); } } return res; @@ -125,7 +125,7 @@ export class Params implements IParams { */ public reset(): void { this.length = 0; - this.subParamsLength = 0; + this._subParamsLength = 0; this._rejectDigits = false; this._rejectSubDigits = false; } @@ -142,7 +142,7 @@ export class Params implements IParams { this._rejectDigits = true; return; } - this._subParamsIdx[this.length] = this.subParamsLength << 8 | this.subParamsLength; + this._subParamsIdx[this.length] = this._subParamsLength << 8 | this._subParamsLength; this.params[this.length++] = value; } @@ -154,11 +154,11 @@ export class Params implements IParams { * sub parameter will be ignored. */ public addSubParam(value: number): void { - if (!this.length || this.subParamsLength >= this.maxSubParamsLength) { + if (!this.length || this._subParamsLength >= this.maxSubParamsLength) { this._rejectSubDigits = true; return; } - this.subParams[this.subParamsLength++] = value; + this._subParams[this._subParamsLength++] = value; this._subParamsIdx[this.length - 1]++; } @@ -178,7 +178,7 @@ export class Params implements IParams { const start = this._subParamsIdx[idx] >> 8; const end = this._subParamsIdx[idx] & 0xFF; if (end - start > 0) { - return this.subParams.subarray(start, end); + return this._subParams.subarray(start, end); } return null; } @@ -194,7 +194,7 @@ export class Params implements IParams { const start = this._subParamsIdx[i] >> 8; const end = this._subParamsIdx[i] & 0xFF; if (end - start > 0) { - result[i] = this.subParams.slice(start, end); + result[i] = this._subParams.slice(start, end); } } return result; @@ -218,13 +218,13 @@ export class Params implements IParams { * Do not use this method directly, consider using `addSubParam` instead. */ public addSubParamDigit(value: number): void { - if (!this.subParamsLength || this._rejectDigits || this._rejectSubDigits) { + if (!this._subParamsLength || this._rejectDigits || this._rejectSubDigits) { return; } - if (this.subParams[this.subParamsLength - 1] === -1) { - this.subParams[this.subParamsLength - 1] = value; + if (this._subParams[this._subParamsLength - 1] === -1) { + this._subParams[this._subParamsLength - 1] = value; } else { - this.subParams[this.subParamsLength - 1] = this.subParams[this.subParamsLength - 1] * 10 + value; + this._subParams[this._subParamsLength - 1] = this._subParams[this._subParamsLength - 1] * 10 + value; } } } diff --git a/src/common/parser/Types.d.ts b/src/common/parser/Types.d.ts index 17f57377..e25b8cc3 100644 --- a/src/common/parser/Types.d.ts +++ b/src/common/parser/Types.d.ts @@ -25,10 +25,6 @@ export interface IParams { params: Int16Array; length: number; - /** sub params and its length */ - subParams: Int16Array; - subParamsLength: number; - /** methods */ clone(): IParams; toArray(): (number | number[])[]; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index d2c16e1d..6e5377db 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -928,23 +928,16 @@ declare module 'xterm' { interface IParams { /** from ctor */ - maxLength: number; - maxSubParamsLength: number; + readonly maxLength: number; + readonly maxSubParamsLength: number; /** param values and its length */ - params: Int16Array; - length: number; + readonly params: Int16Array; + readonly length: number; - /** sub params and its length */ - subParams: Int16Array; - subParamsLength: number; - - /** methods */ + /** exported methods */ clone(): IParams; toArray(): (number | number[])[]; - reset(): void; - addParam(value: number): void; - addSubParam(value: number): void; hasSubParams(idx: number): boolean; getSubParams(idx: number): Int16Array | null; getSubParamsAll(): {[idx: number]: Int16Array}; From 8ce67cb880a4dce32eb66c00bcddc8d77c2dd072 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 18 Jun 2019 18:55:24 +0200 Subject: [PATCH 13/25] add COLORTERM to env in demo --- demo/server.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/demo/server.js b/demo/server.js index 3b170813..8955431b 100644 --- a/demo/server.js +++ b/demo/server.js @@ -37,14 +37,16 @@ function startServer() { }); app.post('/terminals', function (req, res) { + const env = Object.assign({}, process.env); + env['COLORTERM'] = 'truecolor'; var cols = parseInt(req.query.cols), rows = parseInt(req.query.rows), term = pty.spawn(process.platform === 'win32' ? 'cmd.exe' : 'bash', [], { name: 'xterm-256color', cols: cols || 80, rows: rows || 24, - cwd: process.env.PWD, - env: process.env, + cwd: env.PWD, + env: env, encoding: USE_BINARY_UTF8 ? null : 'utf8' }); From 5827b4b0d1b673573ed1d3ed656901af1c9c672d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 2 Jul 2019 15:18:39 +0200 Subject: [PATCH 14/25] change to int32_t --- src/common/parser/Params.test.ts | 6 +++--- src/common/parser/Params.ts | 16 ++++++++-------- src/common/parser/Types.d.ts | 6 +++--- typings/xterm.d.ts | 6 +++--- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/common/parser/Params.test.ts b/src/common/parser/Params.test.ts index dca5f1c2..a874e9b9 100644 --- a/src/common/parser/Params.test.ts +++ b/src/common/parser/Params.test.ts @@ -6,7 +6,7 @@ import { assert } from 'chai'; import { Params } from 'common/parser/Params'; class TestParams extends Params { - public get subParams(): Int16Array { + public get subParams(): Int32Array { return this._subParams; } public get subParamsLength(): number { @@ -129,7 +129,7 @@ describe('Params', () => { it('hasSubParams / getSubParams', () => { const params = Params.fromArray([38, [2, 50, 100, 150], 5, [], 6]); assert.equal(params.hasSubParams(0), true); - assert.deepEqual(params.getSubParams(0), new Int16Array([2, 50, 100, 150])); + assert.deepEqual(params.getSubParams(0), new Int32Array([2, 50, 100, 150])); assert.equal(params.hasSubParams(1), false); assert.deepEqual(params.getSubParams(1), null); assert.equal(params.hasSubParams(2), false); @@ -137,7 +137,7 @@ describe('Params', () => { }); it('getSubParamsAll', () => { const params = Params.fromArray([1, [2, 3], 7, 12345, [-1]]); - assert.deepEqual(params.getSubParamsAll(), {0: new Int16Array([2, 3]), 2: new Int16Array([-1])}); + assert.deepEqual(params.getSubParamsAll(), {0: new Int32Array([2, 3]), 2: new Int32Array([-1])}); }); describe('parse tests', () => { it('param defaults to 0 (ZDM - zero default mode)', () => { diff --git a/src/common/parser/Params.ts b/src/common/parser/Params.ts index 16b9d74e..1d935e0b 100644 --- a/src/common/parser/Params.ts +++ b/src/common/parser/Params.ts @@ -34,13 +34,13 @@ import { IParams } from 'common/parser/Types'; * - max. value for a single (sub) param is 2^15 (caveat: will overflow to negative values) * - max. 256 sub params possible */ -export class Params { +export class Params implements IParams { // params store and length - public params: Int16Array; + public params: Int32Array; public length: number; // sub params store and length - protected _subParams: Int16Array; + protected _subParams: Int32Array; protected _subParamsLength: number; // sub params offsets from param: param idx --> [start, end] offset @@ -79,9 +79,9 @@ export class Params { if (maxSubParamsLength > 256) { throw new Error('maxSubParamsLength must not be greater than 256'); } - this.params = new Int16Array(maxLength); + this.params = new Int32Array(maxLength); this.length = 0; - this._subParams = new Int16Array(maxSubParamsLength); + this._subParams = new Int32Array(maxSubParamsLength); this._subParamsLength = 0; this._subParamsIdx = new Uint16Array(maxLength); this._rejectDigits = false; @@ -174,7 +174,7 @@ export class Params { * Note: The values are borrowed, thus you need to copy * the values if you need to hold them in nonlocal scope. */ - public getSubParams(idx: number): Int16Array | null { + public getSubParams(idx: number): Int32Array | null { const start = this._subParamsIdx[idx] >> 8; const end = this._subParamsIdx[idx] & 0xFF; if (end - start > 0) { @@ -188,8 +188,8 @@ export class Params { * Note: The values are not borrowed, thus it is safe to hold * them without copying. */ - public getSubParamsAll(): {[idx: number]: Int16Array} { - const result: {[idx: number]: Int16Array} = {}; + public getSubParamsAll(): {[idx: number]: Int32Array} { + const result: {[idx: number]: Int32Array} = {}; for (let i = 0; i < this.length; ++i) { const start = this._subParamsIdx[i] >> 8; const end = this._subParamsIdx[i] & 0xFF; diff --git a/src/common/parser/Types.d.ts b/src/common/parser/Types.d.ts index e25b8cc3..436c70af 100644 --- a/src/common/parser/Types.d.ts +++ b/src/common/parser/Types.d.ts @@ -22,7 +22,7 @@ export interface IParams { maxSubParamsLength: number; /** param values and its length */ - params: Int16Array; + params: Int32Array; length: number; /** methods */ @@ -32,8 +32,8 @@ export interface IParams { addParam(value: number): void; addSubParam(value: number): void; hasSubParams(idx: number): boolean; - getSubParams(idx: number): Int16Array | null; - getSubParamsAll(): {[idx: number]: Int16Array}; + getSubParams(idx: number): Int32Array | null; + getSubParamsAll(): {[idx: number]: Int32Array}; } /** diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 2f161757..5ec16ca7 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -938,14 +938,14 @@ declare module 'xterm' { readonly maxSubParamsLength: number; /** param values and its length */ - readonly params: Int16Array; + readonly params: Int32Array; readonly length: number; /** exported methods */ clone(): IParams; toArray(): (number | number[])[]; hasSubParams(idx: number): boolean; - getSubParams(idx: number): Int16Array | null; - getSubParamsAll(): {[idx: number]: Int16Array}; + getSubParams(idx: number): Int32Array | null; + getSubParamsAll(): {[idx: number]: Int32Array}; } } From 7dd5dfab7ab4b451142856833d5612e4f00d8cee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 2 Jul 2019 16:14:28 +0200 Subject: [PATCH 15/25] make params mandatory --- src/InputHandler.ts | 25 ++++++++-------- src/Types.d.ts | 72 ++++++++++++++++++++++----------------------- 2 files changed, 49 insertions(+), 48 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 68dfc3d5..08b1e84b 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -1237,13 +1237,13 @@ export class InputHandler extends Disposable implements IInputHandler { */ public setMode(params: IParams, collect?: string): void { for (let i = 0; i < params.length; i++) { - this._setMode([params.params[i]], collect); + this._setMode(params.params[i], collect); } } - private _setMode(params: number[], collect?: string): void { + private _setMode(param: number, collect?: string): void { if (!collect) { - switch (params[0]) { + switch (param) { case 4: this._terminal.insertMode = true; break; @@ -1252,7 +1252,7 @@ export class InputHandler extends Disposable implements IInputHandler { break; } } else if (collect === '?') { - switch (params[0]) { + switch (param) { case 1: this._terminal.applicationCursor = true; break; @@ -1295,9 +1295,9 @@ export class InputHandler extends Disposable implements IInputHandler { // TODO: Why are params[0] compares nested within a switch for params[0]? - this._terminal.x10Mouse = params[0] === 9; - this._terminal.vt200Mouse = params[0] === 1000; - this._terminal.normalMouse = params[0] > 1000; + this._terminal.x10Mouse = param === 9; + this._terminal.vt200Mouse = param === 1000; + this._terminal.normalMouse = param > 1000; this._terminal.mouseEvents = true; if (this._terminal.element) { this._terminal.element.classList.add('enable-mouse-events'); @@ -1440,13 +1440,13 @@ export class InputHandler extends Disposable implements IInputHandler { */ public resetMode(params: IParams, collect?: string): void { for (let i = 0; i < params.length; i++) { - this._resetMode([params.params[i]], collect); + this._resetMode(params.params[i], collect); } } - private _resetMode(params: number[], collect?: string): void { + private _resetMode(param: number, collect?: string): void { if (!collect) { - switch (params[0]) { + switch (param) { case 4: this._terminal.insertMode = false; break; @@ -1455,7 +1455,7 @@ export class InputHandler extends Disposable implements IInputHandler { break; } } else if (collect === '?') { - switch (params[0]) { + switch (param) { case 1: this._terminal.applicationCursor = false; break; @@ -1520,7 +1520,7 @@ export class InputHandler extends Disposable implements IInputHandler { case 1047: // normal screen buffer - clearing it first // Ensure the selection manager has the correct buffer this._terminal.buffers.activateNormalBuffer(); - if (params[0] === 1049) { + if (param === 1049) { this.restoreCursor(); } this._terminal.refresh(0, this._terminal.rows - 1); @@ -1900,6 +1900,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Set Scrolling Region [top;bottom] (default = full size of win- * dow) (DECSTBM). * CSI ? Pm r + * currently skipped */ public setScrollRegion(params: IParams, collect?: string): void { if (collect) { diff --git a/src/Types.d.ts b/src/Types.d.ts index 1cab66ef..a70c142d 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -113,42 +113,42 @@ export interface IInputHandler { /** C0 SO */ shiftOut(): void; /** C0 SI */ shiftIn(): void; - /** CSI @ */ insertChars(params?: IParams): void; - /** CSI A */ cursorUp(params?: IParams): void; - /** CSI B */ cursorDown(params?: IParams): void; - /** CSI C */ cursorForward(params?: IParams): void; - /** CSI D */ cursorBackward(params?: IParams): void; - /** CSI E */ cursorNextLine(params?: IParams): void; - /** CSI F */ cursorPrecedingLine(params?: IParams): void; - /** CSI G */ cursorCharAbsolute(params?: IParams): void; - /** CSI H */ cursorPosition(params?: IParams): void; - /** CSI I */ cursorForwardTab(params?: IParams): void; - /** CSI J */ eraseInDisplay(params?: IParams): void; - /** CSI K */ eraseInLine(params?: IParams): void; - /** CSI L */ insertLines(params?: IParams): void; - /** CSI M */ deleteLines(params?: IParams): void; - /** CSI P */ deleteChars(params?: IParams): void; - /** CSI S */ scrollUp(params?: IParams): void; - /** CSI T */ scrollDown(params?: IParams, collect?: string): void; - /** CSI X */ eraseChars(params?: IParams): void; - /** CSI Z */ cursorBackwardTab(params?: IParams): void; - /** CSI ` */ charPosAbsolute(params?: IParams): void; - /** CSI a */ hPositionRelative(params?: IParams): void; - /** CSI b */ repeatPrecedingCharacter(params?: IParams): void; - /** CSI c */ sendDeviceAttributes(params?: IParams, collect?: string): void; - /** CSI d */ linePosAbsolute(params?: IParams): void; - /** CSI e */ vPositionRelative(params?: IParams): void; - /** CSI f */ hVPosition(params?: IParams): void; - /** CSI g */ tabClear(params?: IParams): void; - /** CSI h */ setMode(params?: IParams, collect?: string): void; - /** CSI l */ resetMode(params?: IParams, collect?: string): void; - /** CSI m */ charAttributes(params?: IParams): void; - /** CSI n */ deviceStatus(params?: IParams, collect?: string): void; - /** CSI p */ softReset(params?: IParams, collect?: string): void; - /** CSI q */ setCursorStyle(params?: IParams, collect?: string): void; - /** CSI r */ setScrollRegion(params?: IParams, collect?: string): void; - /** CSI s */ saveCursor(params?: IParams): void; - /** CSI u */ restoreCursor(params?: IParams): void; + /** CSI @ */ insertChars(params: IParams): void; + /** CSI A */ cursorUp(params: IParams): void; + /** CSI B */ cursorDown(params: IParams): void; + /** CSI C */ cursorForward(params: IParams): void; + /** CSI D */ cursorBackward(params: IParams): void; + /** CSI E */ cursorNextLine(params: IParams): void; + /** CSI F */ cursorPrecedingLine(params: IParams): void; + /** CSI G */ cursorCharAbsolute(params: IParams): void; + /** CSI H */ cursorPosition(params: IParams): void; + /** CSI I */ cursorForwardTab(params: IParams): void; + /** CSI J */ eraseInDisplay(params: IParams): void; + /** CSI K */ eraseInLine(params: IParams): void; + /** CSI L */ insertLines(params: IParams): void; + /** CSI M */ deleteLines(params: IParams): void; + /** CSI P */ deleteChars(params: IParams): void; + /** CSI S */ scrollUp(params: IParams): void; + /** CSI T */ scrollDown(params: IParams, collect?: string): void; + /** CSI X */ eraseChars(params: IParams): void; + /** CSI Z */ cursorBackwardTab(params: IParams): void; + /** CSI ` */ charPosAbsolute(params: IParams): void; + /** CSI a */ hPositionRelative(params: IParams): void; + /** CSI b */ repeatPrecedingCharacter(params: IParams): void; + /** CSI c */ sendDeviceAttributes(params: IParams, collect?: string): void; + /** CSI d */ linePosAbsolute(params: IParams): void; + /** CSI e */ vPositionRelative(params: IParams): void; + /** CSI f */ hVPosition(params: IParams): void; + /** CSI g */ tabClear(params: IParams): void; + /** CSI h */ setMode(params: IParams, collect?: string): void; + /** CSI l */ resetMode(params: IParams, collect?: string): void; + /** CSI m */ charAttributes(params: IParams): void; + /** CSI n */ deviceStatus(params: IParams, collect?: string): void; + /** CSI p */ softReset(params: IParams, collect?: string): void; + /** CSI q */ setCursorStyle(params: IParams, collect?: string): void; + /** CSI r */ setScrollRegion(params: IParams, collect?: string): void; + /** CSI s */ saveCursor(params: IParams): void; + /** CSI u */ restoreCursor(params: IParams): void; /** OSC 0 OSC 2 */ setTitle(data: string): void; /** ESC E */ nextLine(): void; From 357faa71df98ec5c3c409817f7216db157bbf599 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 2 Jul 2019 18:27:13 +0200 Subject: [PATCH 16/25] value range restrictions: clamp max, reject < -1 --- src/common/parser/Params.test.ts | 24 ++++++++++++++++++++++++ src/common/parser/Params.ts | 30 +++++++++++++++++++++++------- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/src/common/parser/Params.test.ts b/src/common/parser/Params.test.ts index a874e9b9..3278287e 100644 --- a/src/common/parser/Params.test.ts +++ b/src/common/parser/Params.test.ts @@ -192,4 +192,28 @@ describe('Params', () => { assert.deepEqual(params.toArray(), [0, 4, 38, [2, -1, 50, 100, 150], 48, [5, 22]]); }); }); + describe('should not overflow to negative', () => { + it('reject params lesser -1', () => { + const params = new Params(); + params.addParam(-1); + assert.throws(() => params.addParam(-2), 'values lesser than -1 are not allowed'); + }); + it('reject subparams lesser -1', () => { + const params = new Params(); + params.addParam(-1); + params.addSubParam(-1); + assert.throws(() => params.addSubParam(-2), 'values lesser than -1 are not allowed'); + assert.deepEqual(params.toArray(), [-1, [-1]]); + }); + it('clamp parsed params', () => { + const params = new Params(); + parse(params, '2147483648'); + assert.deepEqual(params.toArray(), [0x7FFFFFFF]); + }); + it('clamp parsed subparams', () => { + const params = new Params(); + parse(params, ':2147483648'); + assert.deepEqual(params.toArray(), [0, [0x7FFFFFFF]]); + }); + }); }); diff --git a/src/common/parser/Params.ts b/src/common/parser/Params.ts index 1d935e0b..549dde88 100644 --- a/src/common/parser/Params.ts +++ b/src/common/parser/Params.ts @@ -4,6 +4,11 @@ */ import { IParams } from 'common/parser/Types'; +// max value supported for a single param/subparam - clamp to positive int32 range +const MAX_VALUE = 0x7FFFFFFF; +// max allowed subparams for a single sequence (hardcoded limitation) +const MAX_SUBPARAMS = 256; + /** * Params storage class. * This type is used by the parser to acuumulate sequence parameters and sub parameters @@ -31,7 +36,7 @@ import { IParams } from 'common/parser/Types'; * - never read beyond `params.length - 1` (likely to contain arbitrary data) * - `.getSubParams` returns a borrowed typed array, use `.getSubParamsAll` for cloned sub params * - hardcoded limitations: - * - max. value for a single (sub) param is 2^15 (caveat: will overflow to negative values) + * - max. value for a single (sub) param is 2^31 - 1 * - max. 256 sub params possible */ export class Params implements IParams { @@ -76,7 +81,7 @@ export class Params implements IParams { */ constructor(public maxLength: number = 32, public maxSubParamsLength: number = 32) { // precondition: subparams cannot be more than 256 - if (maxSubParamsLength > 256) { + if (maxSubParamsLength > MAX_SUBPARAMS) { throw new Error('maxSubParamsLength must not be greater than 256'); } this.params = new Int32Array(maxLength); @@ -142,8 +147,11 @@ export class Params implements IParams { this._rejectDigits = true; return; } + if (value < -1) { + throw new Error('values lesser than -1 are not allowed'); + } this._subParamsIdx[this.length] = this._subParamsLength << 8 | this._subParamsLength; - this.params[this.length++] = value; + this.params[this.length++] = value > MAX_VALUE ? MAX_VALUE : value; } /** @@ -154,11 +162,17 @@ export class Params implements IParams { * sub parameter will be ignored. */ public addSubParam(value: number): void { - if (!this.length || this._subParamsLength >= this.maxSubParamsLength) { + if (!this.length) { + return; + } + if (this._subParamsLength >= this.maxSubParamsLength) { this._rejectSubDigits = true; return; } - this._subParams[this._subParamsLength++] = value; + if (value < -1) { + throw new Error('values lesser than -1 are not allowed'); + } + this._subParams[this._subParamsLength++] = value > MAX_VALUE ? MAX_VALUE : value; this._subParamsIdx[this.length - 1]++; } @@ -209,7 +223,8 @@ export class Params implements IParams { if (this._rejectDigits) { return; } - this.params[this.length - 1] = this.params[this.length - 1] * 10 + value; + const v = this.params[this.length - 1] * 10 + value; + this.params[this.length - 1] = v > MAX_VALUE ? MAX_VALUE : v; } /** @@ -224,7 +239,8 @@ export class Params implements IParams { if (this._subParams[this._subParamsLength - 1] === -1) { this._subParams[this._subParamsLength - 1] = value; } else { - this._subParams[this._subParamsLength - 1] = this._subParams[this._subParamsLength - 1] * 10 + value; + const v = this._subParams[this._subParamsLength - 1] * 10 + value; + this._subParams[this._subParamsLength - 1] = v > MAX_VALUE ? MAX_VALUE : v; } } } From f276150ce4f9e9cd913f9ebbc91967de54aec61a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 2 Jul 2019 20:41:21 +0200 Subject: [PATCH 17/25] remove IParams from public API --- src/public/Terminal.ts | 7 ++++--- typings/xterm.d.ts | 23 ++++------------------- 2 files changed, 8 insertions(+), 22 deletions(-) diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index 03c7d36a..f06d003a 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi, IParams } from 'xterm'; +import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi } from 'xterm'; import { ITerminal } from '../Types'; import { IBufferLine } from 'common/Types'; import { IBuffer } from 'common/buffer/Types'; @@ -11,6 +11,7 @@ import { Terminal as TerminalCore } from '../Terminal'; import * as Strings from '../browser/LocalizableStrings'; import { IEvent } from 'common/EventEmitter'; import { AddonManager } from './AddonManager'; +import { IParams } from 'common/parser/Types'; export class Terminal implements ITerminalApi { private _core: ITerminal; @@ -56,8 +57,8 @@ export class Terminal implements ITerminalApi { public attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void { this._core.attachCustomKeyEventHandler(customKeyEventHandler); } - public addCsiHandler(flag: string, callback: (params: IParams, collect: string) => boolean): IDisposable { - return this._core.addCsiHandler(flag, callback); + public addCsiHandler(flag: string, callback: (params: (number | number[])[], collect: string) => boolean): IDisposable { + return this._core.addCsiHandler(flag, (params: IParams, collect: string) => callback(params.toArray(), collect)); } public addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable { return this._core.addOscHandler(ident, callback); diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 5ec16ca7..1f308132 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -494,12 +494,14 @@ declare module 'xterm' { * final character (e.g "m" for SGR) of the CSI sequence. * @param callback The function to handle the escape sequence. The callback * is called with the numerical params, as well as the special characters - * (e.g. "$" for DECSCPP). Return true if the sequence was handled; false if + * (e.g. "$" for DECSCPP). If the sequence has subparams the array will + * contain subarrays with the their numercial values. + * Return true if the sequence was handled; false if * we should try a previous handler (set by addCsiHandler or setCsiHandler). * The most recently-added handler is tried first. * @return An IDisposable you can call to remove this handler. */ - addCsiHandler(flag: string, callback: (params: IParams, collect: string) => boolean): IDisposable; + addCsiHandler(flag: string, callback: (params: (number | number[])[], collect: string) => boolean): IDisposable; /** * (EXPERIMENTAL) Adds a handler for OSC escape sequences. @@ -931,21 +933,4 @@ declare module 'xterm' { */ readonly width: number; } - - interface IParams { - /** from ctor */ - readonly maxLength: number; - readonly maxSubParamsLength: number; - - /** param values and its length */ - readonly params: Int32Array; - readonly length: number; - - /** exported methods */ - clone(): IParams; - toArray(): (number | number[])[]; - hasSubParams(idx: number): boolean; - getSubParams(idx: number): Int32Array | null; - getSubParamsAll(): {[idx: number]: Int32Array}; - } } From 65b757bc19bcf428e9ab64cc815de2be69c82dd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 2 Jul 2019 22:04:12 +0200 Subject: [PATCH 18/25] fix typos in docs --- src/common/parser/Params.ts | 17 ++++++++--------- typings/xterm.d.ts | 2 +- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/common/parser/Params.ts b/src/common/parser/Params.ts index 549dde88..8a66a81a 100644 --- a/src/common/parser/Params.ts +++ b/src/common/parser/Params.ts @@ -4,14 +4,14 @@ */ import { IParams } from 'common/parser/Types'; -// max value supported for a single param/subparam - clamp to positive int32 range +// max value supported for a single param/subparam (clamped to positive int32 range) const MAX_VALUE = 0x7FFFFFFF; // max allowed subparams for a single sequence (hardcoded limitation) const MAX_SUBPARAMS = 256; /** * Params storage class. - * This type is used by the parser to acuumulate sequence parameters and sub parameters + * This type is used by the parser to accumulate sequence parameters and sub parameters * and transmit them to the input handler actions. * * Usage in action handler: @@ -36,8 +36,9 @@ const MAX_SUBPARAMS = 256; * - never read beyond `params.length - 1` (likely to contain arbitrary data) * - `.getSubParams` returns a borrowed typed array, use `.getSubParamsAll` for cloned sub params * - hardcoded limitations: - * - max. value for a single (sub) param is 2^31 - 1 + * - max. value for a single (sub) param is 2^31 - 1 (greater values are clamped to that) * - max. 256 sub params possible + * - negative values are not allowed beside -1 (placeholder for default value) */ export class Params implements IParams { // params store and length @@ -80,7 +81,6 @@ export class Params implements IParams { * @param maxSubParamsLength max length of storable sub parameters */ constructor(public maxLength: number = 32, public maxSubParamsLength: number = 32) { - // precondition: subparams cannot be more than 256 if (maxSubParamsLength > MAX_SUBPARAMS) { throw new Error('maxSubParamsLength must not be greater than 256'); } @@ -94,7 +94,7 @@ export class Params implements IParams { } /** - * Clone object to its own copy. + * Clone object. */ public clone(): Params { const newParams = new Params(this.maxLength, this.maxSubParamsLength); @@ -157,7 +157,7 @@ export class Params implements IParams { /** * Add a sub parameter value. * The sub parameter is automatically associated with the last parameter value. - * If there is no parameter yet the sub parameter is ingored. + * Thus it is not possible to add a subparameter without any parameter added yet. * `Params` only stores up to `subParamsLength` sub parameters, any later * sub parameter will be ignored. */ @@ -198,9 +198,8 @@ export class Params implements IParams { } /** - * Return all kown sub parameters as {idx: subparams} mapping. - * Note: The values are not borrowed, thus it is safe to hold - * them without copying. + * Return all sub parameters as {idx: subparams} mapping. + * Note: The values are not borrowed. */ public getSubParamsAll(): {[idx: number]: Int32Array} { const result: {[idx: number]: Int32Array} = {}; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 1f308132..7a2eb364 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -495,7 +495,7 @@ declare module 'xterm' { * @param callback The function to handle the escape sequence. The callback * is called with the numerical params, as well as the special characters * (e.g. "$" for DECSCPP). If the sequence has subparams the array will - * contain subarrays with the their numercial values. + * contain subarrays with their numercial values. * Return true if the sequence was handled; false if * we should try a previous handler (set by addCsiHandler or setCsiHandler). * The most recently-added handler is tried first. From 17ca9585426b703d25c06edd55c940b7fbef1087 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 2 Jul 2019 22:38:20 +0200 Subject: [PATCH 19/25] js array integration test for addCsiHandler --- test/api/InputHandler.api.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/api/InputHandler.api.ts b/test/api/InputHandler.api.ts index cbf5dbd6..d9c9bf89 100644 --- a/test/api/InputHandler.api.ts +++ b/test/api/InputHandler.api.ts @@ -309,6 +309,23 @@ describe('InputHandler Integration Tests', function(): void { }); }); }); + + describe('addCsiHandler', () => { + it('should call custom CSI handler with js array params', async () => { + await page.evaluate(` + window.term.reset(); + const _customCsiHandlerParams = []; + const _customCsiHandler = window.term.addCsiHandler('m', (params, collect) => { + _customCsiHandlerParams.push(params); + return false; + }, ''); + `); + await page.evaluate(` + window.term.write('\x1b[38;5;123mparams\x1b[38:2::50:100:150msubparams'); + `); + assert.deepEqual(await page.evaluate(`(() => _customCsiHandlerParams)();`), [[38, 5, 123], [38, [2, -1, 50, 100, 150]]]); + }); + }); }); async function openTerminal(options: ITerminalOptions = {}): Promise { From 6bcd8e3bfdde51d5470de3a4f20c4521b51bae6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 4 Jul 2019 12:12:11 +0200 Subject: [PATCH 20/25] ParamsArray to Types.ts --- src/common/parser/EscapeSequenceParser.test.ts | 5 ++--- src/common/parser/Params.test.ts | 3 ++- src/common/parser/Params.ts | 8 ++++---- src/common/parser/Types.d.ts | 4 +++- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/common/parser/EscapeSequenceParser.test.ts b/src/common/parser/EscapeSequenceParser.test.ts index 6a1e20ef..a5d35de9 100644 --- a/src/common/parser/EscapeSequenceParser.test.ts +++ b/src/common/parser/EscapeSequenceParser.test.ts @@ -3,14 +3,13 @@ * @license MIT */ -import { IDcsHandler, IParsingState, IParams } from 'common/parser/Types'; +import { IDcsHandler, IParsingState, IParams, ParamsArray } from 'common/parser/Types'; import { EscapeSequenceParser, TransitionTable, VT500_TRANSITION_TABLE } from 'common/parser/EscapeSequenceParser'; import * as chai from 'chai'; import { StringToUtf32, stringFromCodePoint } from 'common/input/TextDecoder'; import { ParserState } from 'common/parser/Constants'; import { Params } from 'common/parser/Params'; -type ParamsArray = (number | number[])[]; function r(a: number, b: number): string[] { let c = b - a; @@ -30,7 +29,7 @@ class TestEscapeSequenceParser extends EscapeSequenceParser { this._osc = value; } public get params(): ParamsArray { - return this._params.toArray() as ParamsArray; + return this._params.toArray(); } public set params(value: ParamsArray) { this._params = Params.fromArray(value); diff --git a/src/common/parser/Params.test.ts b/src/common/parser/Params.test.ts index 3278287e..01287b8f 100644 --- a/src/common/parser/Params.test.ts +++ b/src/common/parser/Params.test.ts @@ -4,6 +4,7 @@ */ import { assert } from 'chai'; import { Params } from 'common/parser/Params'; +import { ParamsArray } from 'common/parser/Types'; class TestParams extends Params { public get subParams(): Int32Array { @@ -108,7 +109,7 @@ describe('Params', () => { assert.deepEqual(params.toArray(), [1, [2, 3], 12345, [-1]]); }); it('Params.fromArray --> toArray', () => { - let data: (number | number[])[] = []; + let data: ParamsArray = []; assert.deepEqual(Params.fromArray(data).toArray(), data); data = [1, [2, 3], 12345, [-1]]; assert.deepEqual(Params.fromArray(data).toArray(), data); diff --git a/src/common/parser/Params.ts b/src/common/parser/Params.ts index 8a66a81a..1ded5dcf 100644 --- a/src/common/parser/Params.ts +++ b/src/common/parser/Params.ts @@ -2,7 +2,7 @@ * Copyright (c) 2019 The xterm.js authors. All rights reserved. * @license MIT */ -import { IParams } from 'common/parser/Types'; +import { IParams, ParamsArray } from 'common/parser/Types'; // max value supported for a single param/subparam (clamped to positive int32 range) const MAX_VALUE = 0x7FFFFFFF; @@ -57,7 +57,7 @@ export class Params implements IParams { /** * Create a `Params` type from JS array representation. */ - public static fromArray(values: (number | number[])[]): Params { + public static fromArray(values: ParamsArray): Params { const params = new Params(); if (!values.length) { return params; @@ -112,8 +112,8 @@ export class Params implements IParams { * sequence: "1;2:3:4;5::6" * array : [1, 2, [3, 4], 5, [-1, 6]] */ - public toArray(): (number | number[])[] { - const res: (number | number[])[] = []; + public toArray(): ParamsArray { + const res: ParamsArray = []; for (let i = 0; i < this.length; ++i) { res.push(this.params[i]); const start = this._subParamsIdx[i] >> 8; diff --git a/src/common/parser/Types.d.ts b/src/common/parser/Types.d.ts index 436c70af..5f925690 100644 --- a/src/common/parser/Types.d.ts +++ b/src/common/parser/Types.d.ts @@ -16,6 +16,8 @@ export interface IParamsConstructor { fromArray(values: (number | number[])[]): IParams; } +export type ParamsArray = (number | number[])[]; + export interface IParams { /** from ctor */ maxLength: number; @@ -27,7 +29,7 @@ export interface IParams { /** methods */ clone(): IParams; - toArray(): (number | number[])[]; + toArray(): ParamsArray; reset(): void; addParam(value: number): void; addSubParam(value: number): void; From 75d41abf33efacb79c52627d0df53317a82bae6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 4 Jul 2019 12:37:32 +0200 Subject: [PATCH 21/25] re-enable commented out tests --- src/common/parser/EscapeSequenceParser.test.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/common/parser/EscapeSequenceParser.test.ts b/src/common/parser/EscapeSequenceParser.test.ts index a5d35de9..90ec0ee0 100644 --- a/src/common/parser/EscapeSequenceParser.test.ts +++ b/src/common/parser/EscapeSequenceParser.test.ts @@ -574,7 +574,6 @@ describe('EscapeSequenceParser', function (): void { chai.expect(parser.currentState).equal(ParserState.CSI_PARAM); parser.reset(); }); - /* it('trans CSI_PARAM --> CSI_IGNORE', function (): void { parser.reset(); const chars = ['\x3c', '\x3d', '\x3e', '\x3f']; @@ -586,7 +585,6 @@ describe('EscapeSequenceParser', function (): void { parser.reset(); } }); - */ it('trans CSI_PARAM --> CSI_IGNORE', function (): void { parser.reset(); const chars = ['\x3c', '\x3d', '\x3e', '\x3f']; @@ -595,7 +593,7 @@ describe('EscapeSequenceParser', function (): void { parser.currentState = ParserState.CSI_PARAM; parse(parser, '\x3b' + chars[i]); chai.expect(parser.currentState).equal(ParserState.CSI_IGNORE); - // chai.expect(parser.params).eql([0, 0]); + chai.expect(parser.params).eql([0, 0]); parser.reset(); } }); From 009b3c6ef8d1d96b0ff72fe0c205a39bea45235d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 4 Jul 2019 12:38:14 +0200 Subject: [PATCH 22/25] simply error parsing state --- src/common/parser/EscapeSequenceParser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/parser/EscapeSequenceParser.ts b/src/common/parser/EscapeSequenceParser.ts index dcd99e97..62afcfc1 100644 --- a/src/common/parser/EscapeSequenceParser.ts +++ b/src/common/parser/EscapeSequenceParser.ts @@ -469,7 +469,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP currentState, osc, collect, - params: params, + params, abort: false }); if (inject.abort) return; From 7bd032512891eb17be1c195d0a0b21ba7d07b50f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 4 Jul 2019 12:42:43 +0200 Subject: [PATCH 23/25] remove example snippet from docs --- src/common/parser/Params.ts | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/common/parser/Params.ts b/src/common/parser/Params.ts index 1ded5dcf..8fd86afc 100644 --- a/src/common/parser/Params.ts +++ b/src/common/parser/Params.ts @@ -14,23 +14,6 @@ const MAX_SUBPARAMS = 256; * This type is used by the parser to accumulate sequence parameters and sub parameters * and transmit them to the input handler actions. * - * Usage in action handler: - * ```typescript - * function handler(params: IParams): void { - * for (let i = 0; i < params.length; ++i) { - * // get single param - * const param = params.params[i]; - * ... - * // check for sub params - * if (Params.hasSubParams(i)) { - * // get sub params - * const subparams = params.getSubParams(i); - * ... - * } - * } - * } - * ``` - * * NOTES: * - params object for action handlers is borrowed, use `.toArray` or `.clone` to get a copy * - never read beyond `params.length - 1` (likely to contain arbitrary data) From 6a691f2548836240705970b19ee29f07e1be08cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 4 Jul 2019 13:01:18 +0200 Subject: [PATCH 24/25] fix parser docs --- src/common/parser/EscapeSequenceParser.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/common/parser/EscapeSequenceParser.ts b/src/common/parser/EscapeSequenceParser.ts index 62afcfc1..c24e5950 100644 --- a/src/common/parser/EscapeSequenceParser.ts +++ b/src/common/parser/EscapeSequenceParser.ts @@ -221,7 +221,8 @@ class DcsDummy implements IDcsHandler { * 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. + * NOTE: Other than the original parser from vt100.net this parser supports + * sub parameters in digital parameters separated by colons. * TODO: implement error recovery hook via error handler return values */ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceParser { @@ -399,10 +400,12 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP /** * Parse UTF32 codepoints in `data` up to `length`. + * * Note: For several actions with high data load the parsing is optimized * by using local read ahead loops with hardcoded conditions to * avoid costly table lookups. Make sure that any change of table values - * will be reflected in the loop conditions as well. Affected states/actions: + * will be reflected in the loop conditions as well and vice versa. + * Affected states/actions: * - GROUND:PRINT * - CSI_PARAM:PARAM * - DCS_PARAM:PARAM From 5f8d07ac6279badec6b79f5f4a20d7c339c64f76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 4 Jul 2019 13:50:03 +0200 Subject: [PATCH 25/25] cleanup Types.ts, docs for ZDM --- src/common/parser/EscapeSequenceParser.ts | 25 +++++++++++++++-------- src/common/parser/Params.ts | 5 +++++ src/common/parser/Types.d.ts | 17 +++++++-------- 3 files changed, 31 insertions(+), 16 deletions(-) diff --git a/src/common/parser/EscapeSequenceParser.ts b/src/common/parser/EscapeSequenceParser.ts index c24e5950..364ba803 100644 --- a/src/common/parser/EscapeSequenceParser.ts +++ b/src/common/parser/EscapeSequenceParser.ts @@ -216,13 +216,22 @@ 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: Other than the original parser from vt100.net this parser supports - * sub parameters in digital parameters separated by colons. + * + * This parser is currently hardcoded to operate in ZDM (Zero Default Mode) + * as suggested by the original parser, thus empty parameters are set to 0. + * This this is not in line with the latest ECMA specification + * (ZDM was part of the early specs and got completely removed later on). + * + * Other than the original parser from vt100.net this parser supports + * sub parameters in digital parameters separated by colons. Empty sub parameters + * are set to -1. + * * TODO: implement error recovery hook via error handler return values */ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceParser { @@ -261,7 +270,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP this.currentState = this.initialState; this._osc = ''; this._params = new Params(); // defaults to 32 storable params/subparams - this._params.addParam(0); + this._params.addParam(0); // ZDM this._collect = ''; this.precedingCodepoint = 0; @@ -392,7 +401,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP this.currentState = this.initialState; this._osc = ''; this._params.reset(); - this._params.addParam(0); + this._params.addParam(0); // ZDM this._collect = ''; this._activeDcsHandler = null; this.precedingCodepoint = 0; @@ -502,7 +511,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP do { switch (code) { case 0x3b: - params.addParam(0); + params.addParam(0); // ZDM isSub = false; break; case 0x3a: @@ -528,7 +537,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP case ParserAction.CLEAR: osc = ''; params.reset(); - params.addParam(0); + params.addParam(0); // ZDM collect = ''; break; case ParserAction.DCS_HOOK: @@ -558,7 +567,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP if (code === 0x1b) transition |= ParserState.ESCAPE; osc = ''; params.reset(); - params.addParam(0); + params.addParam(0); // ZDM collect = ''; break; case ParserAction.OSC_START: @@ -605,7 +614,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP if (code === 0x1b) transition |= ParserState.ESCAPE; osc = ''; params.reset(); - params.addParam(0); + params.addParam(0); // ZDM collect = ''; break; } diff --git a/src/common/parser/Params.ts b/src/common/parser/Params.ts index 8fd86afc..7ef8341a 100644 --- a/src/common/parser/Params.ts +++ b/src/common/parser/Params.ts @@ -22,6 +22,11 @@ const MAX_SUBPARAMS = 256; * - max. value for a single (sub) param is 2^31 - 1 (greater values are clamped to that) * - max. 256 sub params possible * - negative values are not allowed beside -1 (placeholder for default value) + * + * About ZDM (Zero Default Mode): + * ZDM is not orchestrated by this class. If the parser is in ZDM, + * it should add 0 for empty params, otherwise -1. This does not apply + * to subparams, empty subparams should always be added with -1. */ export class Params implements IParams { // params store and length diff --git a/src/common/parser/Types.d.ts b/src/common/parser/Types.d.ts index 5f925690..d432800d 100644 --- a/src/common/parser/Types.d.ts +++ b/src/common/parser/Types.d.ts @@ -6,18 +6,18 @@ import { IDisposable } from 'common/Types'; import { ParserState } from 'common/parser/Constants'; -/** - * Params types. - */ +/** sequence params serialized to js arrays */ +export type ParamsArray = (number | number[])[]; + +/** Params constructor type. */ export interface IParamsConstructor { new(maxLength: number, maxSubParamsLength: number): IParams; - /** create params object from array like [1, [2, 3]] */ - fromArray(values: (number | number[])[]): IParams; + /** create params from ParamsArray */ + fromArray(values: ParamsArray): IParams; } -export type ParamsArray = (number | number[])[]; - +/** Interface of Params storage class. */ export interface IParams { /** from ctor */ maxLength: number; @@ -100,13 +100,14 @@ export interface IEscapeSequenceParser extends IDisposable { * It gets reset by the parser for any valid sequence beside REP itself. */ precedingCodepoint: number; + /** * Reset the parser to its initial state (handlers are kept). */ reset(): void; /** - * Parse string `data`. + * Parse UTF32 codepoints in `data` up to `length`. * @param data The data to parse. */ parse(data: Uint32Array, length: number): void;