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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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; From 38f2e7a81dd90f755dedd98fc299a1b54002c64f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 4 Jul 2019 08:28:21 -0700 Subject: [PATCH 26/30] Simplify webgl atlas by removing BaseCharAtlas --- .../src/atlas/BaseCharAtlas.ts | 56 --------- .../src/atlas/CharAtlasCache.ts | 5 +- .../src/atlas/WebglCharAtlas.ts | 26 ++-- .../src/renderLayer/BaseRenderLayer.ts | 113 +----------------- 4 files changed, 16 insertions(+), 184 deletions(-) delete mode 100644 addons/xterm-addon-webgl/src/atlas/BaseCharAtlas.ts diff --git a/addons/xterm-addon-webgl/src/atlas/BaseCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/BaseCharAtlas.ts deleted file mode 100644 index 470736f1..00000000 --- a/addons/xterm-addon-webgl/src/atlas/BaseCharAtlas.ts +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { IGlyphIdentifier } from './Types'; -import { IDisposable } from 'xterm'; - -export abstract class BaseCharAtlas implements IDisposable { - private _didWarmUp: boolean = false; - - public dispose(): void { } - - /** - * Perform any work needed to warm the cache before it can be used. May be called multiple times. - * Implement _doWarmUp instead if you only want to get called once. - */ - public warmUp(): void { - if (!this._didWarmUp) { - this._doWarmUp(); - this._didWarmUp = true; - } - } - - /** - * Perform any work needed to warm the cache before it can be used. Used by the default - * implementation of warmUp(), and will only be called once. - */ - protected _doWarmUp(): void { } - - /** - * Called when we start drawing a new frame. - * - * TODO: We rely on this getting called by TextRenderLayer. This should really be called by - * Renderer instead, but we need to make Renderer the source-of-truth for the char atlas, instead - * of BaseRenderLayer. - */ - public beginFrame(): void { } - - /** - * May be called before warmUp finishes, however it is okay for the implementation to - * do nothing and return false in that case. - * - * @param ctx Where to draw the character onto. - * @param glyph Information about what to draw - * @param x The position on the context to start drawing at - * @param y The position on the context to start drawing at - * @returns The success state. True if we drew the character. - */ - public abstract draw( - ctx: CanvasRenderingContext2D, - glyph: IGlyphIdentifier, - x: number, - y: number - ): boolean; -} diff --git a/addons/xterm-addon-webgl/src/atlas/CharAtlasCache.ts b/addons/xterm-addon-webgl/src/atlas/CharAtlasCache.ts index 647b96b4..5046006f 100644 --- a/addons/xterm-addon-webgl/src/atlas/CharAtlasCache.ts +++ b/addons/xterm-addon-webgl/src/atlas/CharAtlasCache.ts @@ -4,14 +4,13 @@ */ import { generateConfig, configEquals } from './CharAtlasUtils'; -import { BaseCharAtlas } from './BaseCharAtlas'; import { WebglCharAtlas } from './WebglCharAtlas'; import { ICharAtlasConfig } from './Types'; import { Terminal } from 'xterm'; import { IColorSet } from 'browser/Types'; interface ICharAtlasCacheEntry { - atlas: BaseCharAtlas; + atlas: WebglCharAtlas; config: ICharAtlasConfig; // N.B. This implementation potentially holds onto copies of the terminal forever, so // this may cause memory leaks. @@ -31,7 +30,7 @@ export function acquireCharAtlas( colors: IColorSet, scaledCharWidth: number, scaledCharHeight: number -): BaseCharAtlas { +): WebglCharAtlas { const newConfig = generateConfig(scaledCharWidth, scaledCharHeight, terminal, colors); // Check to see if the terminal already owns this config diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 6a941d55..07a97fd0 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -3,14 +3,14 @@ * @license MIT */ -import { IGlyphIdentifier, ICharAtlasConfig } from './Types'; +import { ICharAtlasConfig } from './Types'; import { DIM_OPACITY, INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; -import { BaseCharAtlas } from './BaseCharAtlas'; import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from '../Types'; import { DEFAULT_COLOR, DEFAULT_ATTR } from 'common/buffer/Constants'; import { is256Color } from './CharAtlasUtils'; import { IColor } from 'browser/Types'; import { FLAGS } from '../Constants'; +import { IDisposable } from 'xterm'; // In practice we're probably never going to exhaust a texture this large. For debugging purposes, // however, it can be useful to set this to a really tiny value, to verify that LRU eviction works. @@ -42,7 +42,9 @@ const NULL_RASTERIZED_GLYPH: IRasterizedGlyph = { const TMP_CANVAS_GLYPH_PADDING = 2; -export class WebglCharAtlas extends BaseCharAtlas { +export class WebglCharAtlas implements IDisposable { + private _didWarmUp: boolean = false; + private _cacheMap: { [code: number]: IRasterizedGlyphSet } = {}; private _cacheMapCombined: { [chars: string]: IRasterizedGlyphSet } = {}; @@ -67,8 +69,6 @@ export class WebglCharAtlas extends BaseCharAtlas { private _workBoundingBox: IBoundingBox = { top: 0, left: 0, bottom: 0, right: 0 }; constructor(document: Document, private _config: ICharAtlasConfig) { - super(); - this.cacheCanvas = document.createElement('canvas'); this.cacheCanvas.width = TEXTURE_WIDTH; this.cacheCanvas.height = TEXTURE_HEIGHT; @@ -92,6 +92,13 @@ export class WebglCharAtlas extends BaseCharAtlas { } } + public warmUp(): void { + if (!this._didWarmUp) { + this._doWarmUp(); + this._didWarmUp = true; + } + } + protected _doWarmUp(): void { // Pre-fill with ASCII 33-126 for (let i = 33; i < 126; i++) { @@ -146,15 +153,6 @@ export class WebglCharAtlas extends BaseCharAtlas { return rasterizedGlyph; } - public draw( - ctx: CanvasRenderingContext2D, - glyph: IGlyphIdentifier, - x: number, - y: number - ): boolean { - throw new Error('WebglCharAtlas is only compatible with the webgl renderer'); - } - private _getColorFromAnsiIndex(idx: number): IColor { if (idx >= this._config.colors.ansi.length) { throw new Error('No color found for idx ' + idx); diff --git a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts index 0422440e..029ef308 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts @@ -8,13 +8,13 @@ import { ICellData } from 'common/Types'; import { DEFAULT_COLOR, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from 'common/buffer/Constants'; import { IGlyphIdentifier } from '../atlas/Types'; import { DIM_OPACITY, INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; -import { BaseCharAtlas } from '../atlas/BaseCharAtlas'; import { acquireCharAtlas } from '../atlas/CharAtlasCache'; import { Terminal } from 'xterm'; import { IColorSet } from 'browser/Types'; import { IRenderDimensions } from 'browser/renderer/Types'; import { CellData } from 'common/buffer/CellData'; import { AttributeData } from 'common/buffer/AttributeData'; +import { WebglCharAtlas } from 'atlas/WebglCharAtlas'; export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; @@ -26,7 +26,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { private _scaledCharLeft: number = 0; private _scaledCharTop: number = 0; - protected _charAtlas: BaseCharAtlas; + protected _charAtlas: WebglCharAtlas; /** * An object that's reused when drawing glyphs in order to reduce GC. @@ -249,115 +249,6 @@ export abstract class BaseRenderLayer implements IRenderLayer { y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight / 2); } - /** - * Draws one or more characters at a cell. If possible this will draw using - * the character atlas to reduce draw time. - * @param terminal The terminal. - * @param chars The character or characters. - * @param code The character code. - * @param width The width of the characters. - * @param x The column to draw at. - * @param y The row to draw at. - * @param fg The foreground color, in the format stored within the attributes. - * @param bg The background color, in the format stored within the attributes. - * This is used to validate whether a cached image can be used. - * @param bold Whether the text is bold. - */ - protected _drawChars(terminal: Terminal, cell: ICellData, x: number, y: number): void { - - // skip cache right away if we draw in RGB - // Note: to avoid bad runtime JoinedCellData will be skipped - // in the cache handler itself (atlasDidDraw == false) and - // fall through to uncached later down below - if (cell.isFgRGB() || cell.isBgRGB()) { - this._drawUncachedChars(terminal, cell, x, y); - return; - } - - let fg; - let bg; - if (cell.isInverse()) { - fg = (cell.isBgDefault()) ? INVERTED_DEFAULT_COLOR : cell.getBgColor(); - bg = (cell.isFgDefault()) ? INVERTED_DEFAULT_COLOR : cell.getFgColor(); - } else { - bg = (cell.isBgDefault()) ? DEFAULT_COLOR : cell.getBgColor(); - fg = (cell.isFgDefault()) ? DEFAULT_COLOR : cell.getFgColor(); - } - - const drawInBrightColor = terminal.getOption('drawBoldTextInBrightColors') && cell.isBold() && fg < 8 && fg !== INVERTED_DEFAULT_COLOR; - - fg += drawInBrightColor ? 8 : 0; - this._currentGlyphIdentifier.chars = cell.getChars() || WHITESPACE_CELL_CHAR; - this._currentGlyphIdentifier.code = cell.getCode() || WHITESPACE_CELL_CODE; - this._currentGlyphIdentifier.bg = bg; - this._currentGlyphIdentifier.fg = fg; - this._currentGlyphIdentifier.bold = !!cell.isBold(); - this._currentGlyphIdentifier.dim = !!cell.isDim(); - this._currentGlyphIdentifier.italic = !!cell.isItalic(); - const atlasDidDraw = this._charAtlas && this._charAtlas.draw( - this._ctx, - this._currentGlyphIdentifier, - x * this._scaledCellWidth + this._scaledCharLeft, - y * this._scaledCellHeight + this._scaledCharTop - ); - - if (!atlasDidDraw) { - this._drawUncachedChars(terminal, cell, x, y); - } - } - - /** - * Draws one or more characters at one or more cells. The character(s) will be - * clipped to ensure that they fit with the cell(s), including the cell to the - * right if the last character is a wide character. - * @param terminal The terminal. - * @param chars The character. - * @param width The width of the character. - * @param fg The foreground color, in the format stored within the attributes. - * @param x The column to draw at. - * @param y The row to draw at. - */ - private _drawUncachedChars(terminal: Terminal, cell: ICellData, x: number, y: number): void { - this._ctx.save(); - this._ctx.font = this._getFont(terminal, !!cell.isBold(), !!cell.isItalic()); - this._ctx.textBaseline = 'middle'; - - if (cell.isInverse()) { - if (cell.isBgDefault()) { - this._ctx.fillStyle = this._colors.background.css; - } else if (cell.isBgRGB()) { - this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getBgColor()).join(',')})`; - } else { - this._ctx.fillStyle = this._colors.ansi[cell.getBgColor()].css; - } - } else { - if (cell.isFgDefault()) { - this._ctx.fillStyle = this._colors.foreground.css; - } else if (cell.isFgRGB()) { - this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`; - } else { - let fg = cell.getFgColor(); - if (terminal.getOption('drawBoldTextInBrightColors') && cell.isBold() && fg < 8) { - fg += 8; - } - this._ctx.fillStyle = this._colors.ansi[fg].css; - } - } - - this._clipRow(terminal, y); - - // Apply alpha to dim the character - if (cell.isDim()) { - this._ctx.globalAlpha = DIM_OPACITY; - } - // Draw the character - this._ctx.fillText( - cell.getChars(), - x * this._scaledCellWidth + this._scaledCharLeft, - y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight / 2); - this._ctx.restore(); - } - /** * Clips a row to ensure no pixels will be drawn outside the cells in the row. * @param terminal The terminal. From 0898de147fb538b4ab71074d114669a97effcc97 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 4 Jul 2019 08:36:44 -0700 Subject: [PATCH 27/30] Ensure GlyphRenderer._atlas exists on beginFrame This should fix an exception breaking it in vscode --- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index dc375348..a7bd5e9a 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -165,7 +165,7 @@ export class GlyphRenderer { } public beginFrame(): boolean { - return this._atlas.beginFrame(); + return this._atlas ? this._atlas.beginFrame() : true; } public updateCell(x: number, y: number, code: number, attr: number, bg: number, fg: number, chars: string): void { From 40bc62c3837262147e732e3eb3f2c169b224872c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 4 Jul 2019 08:58:42 -0700 Subject: [PATCH 28/30] Enable strict null checks in webgl addon Part of #2286 --- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 20 +++--- .../src/RectangleRenderer.ts | 10 +-- addons/xterm-addon-webgl/src/WebglRenderer.ts | 26 ++++---- addons/xterm-addon-webgl/src/WebglUtils.ts | 15 +++-- .../src/atlas/CharAtlasUtils.ts | 13 ++-- .../src/atlas/WebglCharAtlas.ts | 5 +- .../src/renderLayer/BaseRenderLayer.ts | 3 +- .../src/renderLayer/CursorRenderLayer.ts | 64 +++++++++---------- .../src/renderLayer/LinkRenderLayer.ts | 6 +- addons/xterm-addon-webgl/src/tsconfig.json | 3 +- 10 files changed, 91 insertions(+), 74 deletions(-) diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index a7bd5e9a..4ae01698 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { createProgram, PROJECTION_MATRIX } from './WebglUtils'; +import { createProgram, PROJECTION_MATRIX, throwIfFalsy } from './WebglUtils'; import { WebglCharAtlas } from './atlas/WebglCharAtlas'; import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types'; import { INDICIES_PER_CELL } from './WebglRenderer'; @@ -104,12 +104,16 @@ export class GlyphRenderer { ) { const gl = this._gl; - this._program = createProgram(gl, vertexShaderSource, fragmentShaderSource); + const program = throwIfFalsy(createProgram(gl, vertexShaderSource, fragmentShaderSource)); + if (program === undefined) { + throw new Error('Could not create WebGL program'); + } + this._program = program; // Uniform locations - this._projectionLocation = gl.getUniformLocation(this._program, 'u_projection'); - this._resolutionLocation = gl.getUniformLocation(this._program, 'u_resolution'); - this._textureLocation = gl.getUniformLocation(this._program, 'u_texture'); + this._projectionLocation = throwIfFalsy(gl.getUniformLocation(this._program, 'u_projection')); + this._resolutionLocation = throwIfFalsy(gl.getUniformLocation(this._program, 'u_resolution')); + this._textureLocation = throwIfFalsy(gl.getUniformLocation(this._program, 'u_texture')); // Create and set the vertex array object this._vertexArrayObject = gl.createVertexArray(); @@ -131,7 +135,7 @@ export class GlyphRenderer { gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, unitQuadElementIndices, gl.STATIC_DRAW); // Setup attributes - this._attributesBuffer = gl.createBuffer(); + this._attributesBuffer = throwIfFalsy(gl.createBuffer()); gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer); gl.enableVertexAttribArray(VertexAttribLocations.OFFSET); gl.vertexAttribPointer(VertexAttribLocations.OFFSET, 2, gl.FLOAT, false, BYTES_PER_CELL, 0); @@ -150,7 +154,7 @@ export class GlyphRenderer { gl.vertexAttribDivisor(VertexAttribLocations.CELL_POSITION, 1); // Setup empty texture atlas - this._atlasTexture = gl.createTexture(); + this._atlasTexture = throwIfFalsy(gl.createTexture()); gl.bindTexture(gl.TEXTURE_2D, this._atlasTexture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0, 0, 255, 255])); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); @@ -264,7 +268,7 @@ export class GlyphRenderer { if (!line) { line = terminal.buffer.getLine(row); } - const chars = line.getCell(x).char; + const chars = line!.getCell(x)!.char; this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], attr, bg, fg, chars); } else { this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], attr, bg, fg); diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index d4ce3b4c..3d755c98 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { createProgram, expandFloat32Array, PROJECTION_MATRIX } from './WebglUtils'; +import { createProgram, expandFloat32Array, PROJECTION_MATRIX, throwIfFalsy } from './WebglUtils'; import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext, ISelectionRenderModel } from './Types'; import { fill } from 'common/TypedArrayUtils'; import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; @@ -83,11 +83,11 @@ export class RectangleRenderer { ) { const gl = this._gl; - this._program = createProgram(gl, vertexShaderSource, fragmentShaderSource); + this._program = throwIfFalsy(createProgram(gl, vertexShaderSource, fragmentShaderSource)); // Uniform locations - this._resolutionLocation = gl.getUniformLocation(this._program, 'u_resolution'); - this._projectionLocation = gl.getUniformLocation(this._program, 'u_projection'); + this._resolutionLocation = throwIfFalsy(gl.getUniformLocation(this._program, 'u_resolution')); + this._projectionLocation = throwIfFalsy(gl.getUniformLocation(this._program, 'u_projection')); // Create and set the vertex array object this._vertexArrayObject = gl.createVertexArray(); @@ -109,7 +109,7 @@ export class RectangleRenderer { gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, unitQuadElementIndices, gl.STATIC_DRAW); // Setup attributes - this._attributesBuffer = gl.createBuffer(); + this._attributesBuffer = throwIfFalsy(gl.createBuffer()); gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer); gl.enableVertexAttribArray(VertexAttribLocations.POSITION); gl.vertexAttribPointer(VertexAttribLocations.POSITION, 2, gl.FLOAT, false, BYTES_PER_RECTANGLE, 0); diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index defed962..93befcec 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -57,18 +57,18 @@ export class WebglRenderer extends Disposable implements IRenderer { new CursorRenderLayer(this._core.screenElement, 3, this._colors) ]; this.dimensions = { - scaledCharWidth: null, - scaledCharHeight: null, - scaledCellWidth: null, - scaledCellHeight: null, - scaledCharLeft: null, - scaledCharTop: null, - scaledCanvasWidth: null, - scaledCanvasHeight: null, - canvasWidth: null, - canvasHeight: null, - actualCellWidth: null, - actualCellHeight: null + scaledCharWidth: 0, + scaledCharHeight: 0, + scaledCellWidth: 0, + scaledCellHeight: 0, + scaledCharLeft: 0, + scaledCharTop: 0, + scaledCanvasWidth: 0, + scaledCanvasHeight: 0, + canvasWidth: 0, + canvasHeight: 0, + actualCellWidth: 0, + actualCellHeight: 0 }; this._devicePixelRatio = window.devicePixelRatio; this._updateDimensions(); @@ -252,7 +252,7 @@ export class WebglRenderer extends Disposable implements IRenderer { for (let y = start; y <= end; y++) { const row = y + terminal.buffer.ydisp; - const line = terminal.buffer.lines.get(row); + const line = terminal.buffer.lines.get(row)!; this._model.lineLengths[y] = 0; for (let x = 0; x < terminal.cols; x++) { const charData = line.get(x); diff --git a/addons/xterm-addon-webgl/src/WebglUtils.ts b/addons/xterm-addon-webgl/src/WebglUtils.ts index 8f166a23..ff62388e 100644 --- a/addons/xterm-addon-webgl/src/WebglUtils.ts +++ b/addons/xterm-addon-webgl/src/WebglUtils.ts @@ -15,9 +15,9 @@ export const PROJECTION_MATRIX = new Float32Array([ ]); export function createProgram(gl: WebGLRenderingContext, vertexSource: string, fragmentSource: string): WebGLProgram | undefined { - const program = gl.createProgram(); - gl.attachShader(program, createShader(gl, gl.VERTEX_SHADER, vertexSource)); - gl.attachShader(program, createShader(gl, gl.FRAGMENT_SHADER, fragmentSource)); + const program = throwIfFalsy(gl.createProgram()); + gl.attachShader(program, throwIfFalsy(createShader(gl, gl.VERTEX_SHADER, vertexSource))); + gl.attachShader(program, throwIfFalsy(createShader(gl, gl.FRAGMENT_SHADER, fragmentSource))); gl.linkProgram(program); const success = gl.getProgramParameter(program, gl.LINK_STATUS); if (success) { @@ -29,7 +29,7 @@ export function createProgram(gl: WebGLRenderingContext, vertexSource: string, f } export function createShader(gl: WebGLRenderingContext, type: number, source: string): WebGLShader | undefined { - const shader = gl.createShader(type); + const shader = throwIfFalsy(gl.createShader(type)); gl.shaderSource(shader, source); gl.compileShader(shader); const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS); @@ -49,3 +49,10 @@ export function expandFloat32Array(source: Float32Array, max: number): Float32Ar } return newArray; } + +export function throwIfFalsy(value: T | undefined | null): T { + if (!value) { + throw new Error('value must not be falsy'); + } + return value; +} diff --git a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts index 1c43dd70..857fa628 100644 --- a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts +++ b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts @@ -6,16 +6,21 @@ import { ICharAtlasConfig } from './Types'; import { DEFAULT_COLOR } from 'common/buffer/Constants'; import { Terminal, FontWeight } from 'xterm'; -import { IColorSet } from 'browser/Types'; +import { IColorSet, IColor } from 'browser/Types'; + +const NULL_COLOR: IColor = { + css: '', + rgba: 0 +}; export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: Terminal, colors: IColorSet): ICharAtlasConfig { // null out some fields that don't matter const clonedColors: IColorSet = { foreground: colors.foreground, background: colors.background, - cursor: null, - cursorAccent: null, - selection: null, + cursor: NULL_COLOR, + cursorAccent: NULL_COLOR, + selection: NULL_COLOR, // For the static char atlas, we only use the first 16 colors, but we need all 256 for the // dynamic character atlas. ansi: colors.ansi.slice() diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 07a97fd0..8d8e995b 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -8,6 +8,7 @@ import { DIM_OPACITY, INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Cons import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from '../Types'; import { DEFAULT_COLOR, DEFAULT_ATTR } from 'common/buffer/Constants'; import { is256Color } from './CharAtlasUtils'; +import { throwIfFalsy } from '../WebglUtils'; import { IColor } from 'browser/Types'; import { FLAGS } from '../Constants'; import { IDisposable } from 'xterm'; @@ -75,12 +76,12 @@ export class WebglCharAtlas implements IDisposable { // The canvas needs alpha because we use clearColor to convert the background color to alpha. // It might also contain some characters with transparent backgrounds if allowTransparency is // set. - this._cacheCtx = this.cacheCanvas.getContext('2d', {alpha: true}); + this._cacheCtx = throwIfFalsy(this.cacheCanvas.getContext('2d', {alpha: true})); this._tmpCanvas = document.createElement('canvas'); this._tmpCanvas.width = this._config.scaledCharWidth * 2 + TMP_CANVAS_GLYPH_PADDING * 2; this._tmpCanvas.height = this._config.scaledCharHeight + TMP_CANVAS_GLYPH_PADDING * 2; - this._tmpCtx = this._tmpCanvas.getContext('2d', {alpha: this._config.allowTransparency}); + this._tmpCtx = throwIfFalsy(this._tmpCanvas.getContext('2d', {alpha: this._config.allowTransparency})); // This is useful for debugging document.body.appendChild(this.cacheCanvas); diff --git a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts index 029ef308..5385e658 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts @@ -15,6 +15,7 @@ import { IRenderDimensions } from 'browser/renderer/Types'; import { CellData } from 'common/buffer/CellData'; import { AttributeData } from 'common/buffer/AttributeData'; import { WebglCharAtlas } from 'atlas/WebglCharAtlas'; +import { throwIfFalsy } from '../WebglUtils'; export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; @@ -63,7 +64,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { } private _initCanvas(): void { - this._ctx = this._canvas.getContext('2d', {alpha: this._alpha}); + this._ctx = throwIfFalsy(this._canvas.getContext('2d', {alpha: this._alpha})); // Draw the background if this is an opaque layer if (!this._alpha) { this._clearAll(); diff --git a/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts index 09abe82a..d0a6aa82 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts @@ -32,11 +32,11 @@ export class CursorRenderLayer extends BaseRenderLayer { constructor(container: HTMLElement, zIndex: number, colors: IColorSet) { super(container, 'cursor', zIndex, true, colors); this._state = { - x: null, - y: null, - isFocused: null, - style: null, - width: null + x: 0, + y: 0, + isFocused: false, + style: '', + width: 0 }; this._cursorRenderers = { 'bar': this._renderBarCursor.bind(this), @@ -50,11 +50,11 @@ export class CursorRenderLayer extends BaseRenderLayer { super.resize(terminal, dim); // Resizing the canvas discards the contents of the canvas so clear state this._state = { - x: null, - y: null, - isFocused: null, - style: null, - width: null + x: 0, + y: 0, + isFocused: false, + style: '', + width: 0 }; } @@ -62,7 +62,6 @@ export class CursorRenderLayer extends BaseRenderLayer { this._clearCursor(); if (this._cursorBlinkStateManager) { this._cursorBlinkStateManager.dispose(); - this._cursorBlinkStateManager = null; this.onOptionsChanged(terminal); } } @@ -92,7 +91,6 @@ export class CursorRenderLayer extends BaseRenderLayer { } else { if (this._cursorBlinkStateManager) { this._cursorBlinkStateManager.dispose(); - this._cursorBlinkStateManager = null; } // Request a refresh from the terminal as management of rendering is being // moved back to the terminal @@ -184,11 +182,11 @@ export class CursorRenderLayer extends BaseRenderLayer { if (this._state) { this._clearCells(this._state.x, this._state.y, this._state.width, 1); this._state = { - x: null, - y: null, - isFocused: null, - style: null, - width: null + x: 0, + y: 0, + isFocused: false, + style: '', + width: 0 }; } } @@ -227,16 +225,16 @@ export class CursorRenderLayer extends BaseRenderLayer { class CursorBlinkStateManager { public isCursorVisible: boolean; - private _animationFrame: number; - private _blinkStartTimeout: number; - private _blinkInterval: number; + private _animationFrame: number | undefined; + private _blinkStartTimeout: number | undefined; + private _blinkInterval: number | undefined; /** * The time at which the animation frame was restarted, this is used on the * next render to restart the timers so they don't need to restart the timers * multiple times over a short period. */ - private _animationTimeRestarted: number; + private _animationTimeRestarted: number | undefined; constructor( terminal: Terminal, @@ -253,15 +251,15 @@ class CursorBlinkStateManager { public dispose(): void { if (this._blinkInterval) { window.clearInterval(this._blinkInterval); - this._blinkInterval = null; + this._blinkInterval = undefined; } if (this._blinkStartTimeout) { window.clearTimeout(this._blinkStartTimeout); - this._blinkStartTimeout = null; + this._blinkStartTimeout = undefined; } if (this._animationFrame) { window.cancelAnimationFrame(this._animationFrame); - this._animationFrame = null; + this._animationFrame = undefined; } } @@ -276,7 +274,7 @@ class CursorBlinkStateManager { if (!this._animationFrame) { this._animationFrame = window.requestAnimationFrame(() => { this._renderCallback(); - this._animationFrame = null; + this._animationFrame = undefined; }); } } @@ -296,7 +294,7 @@ class CursorBlinkStateManager { // started if (this._animationTimeRestarted) { const time = BLINK_INTERVAL - (Date.now() - this._animationTimeRestarted); - this._animationTimeRestarted = null; + this._animationTimeRestarted = undefined; if (time > 0) { this._restartInterval(time); return; @@ -307,7 +305,7 @@ class CursorBlinkStateManager { this.isCursorVisible = false; this._animationFrame = window.requestAnimationFrame(() => { this._renderCallback(); - this._animationFrame = null; + this._animationFrame = undefined; }); // Setup the blink interval @@ -317,7 +315,7 @@ class CursorBlinkStateManager { // calc time diff // Make restart interval do a setTimeout initially? const time = BLINK_INTERVAL - (Date.now() - this._animationTimeRestarted); - this._animationTimeRestarted = null; + this._animationTimeRestarted = undefined; this._restartInterval(time); return; } @@ -326,7 +324,7 @@ class CursorBlinkStateManager { this.isCursorVisible = !this.isCursorVisible; this._animationFrame = window.requestAnimationFrame(() => { this._renderCallback(); - this._animationFrame = null; + this._animationFrame = undefined; }); }, BLINK_INTERVAL); }, timeToStart); @@ -336,20 +334,20 @@ class CursorBlinkStateManager { this.isCursorVisible = true; if (this._blinkInterval) { window.clearInterval(this._blinkInterval); - this._blinkInterval = null; + this._blinkInterval = undefined; } if (this._blinkStartTimeout) { window.clearTimeout(this._blinkStartTimeout); - this._blinkStartTimeout = null; + this._blinkStartTimeout = undefined; } if (this._animationFrame) { window.cancelAnimationFrame(this._animationFrame); - this._animationFrame = null; + this._animationFrame = undefined; } } public resume(terminal: Terminal): void { - this._animationTimeRestarted = null; + this._animationTimeRestarted = undefined; this._restartInterval(); this.restartBlinkAnimation(terminal); } diff --git a/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts index 7c79ddcc..a29d2cfd 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts @@ -12,7 +12,7 @@ import { IColorSet } from 'browser/Types'; import { IRenderDimensions } from 'browser/renderer/Types'; export class LinkRenderLayer extends BaseRenderLayer { - private _state: ILinkifierEvent = null; + private _state: ILinkifierEvent | undefined; constructor(container: HTMLElement, zIndex: number, colors: IColorSet, terminal: ILinkifierAccessor) { super(container, 'link', zIndex, true, colors); @@ -23,7 +23,7 @@ export class LinkRenderLayer extends BaseRenderLayer { public resize(terminal: Terminal, dim: IRenderDimensions): void { super.resize(terminal, dim); // Resizing the canvas discards the contents of the canvas so clear state - this._state = null; + this._state = undefined; } public reset(terminal: Terminal): void { @@ -38,7 +38,7 @@ export class LinkRenderLayer extends BaseRenderLayer { this._clearCells(0, this._state.y1 + 1, this._state.cols, middleRowCount); } this._clearCells(0, this._state.y2, this._state.x2, 1); - this._state = null; + this._state = undefined; } } diff --git a/addons/xterm-addon-webgl/src/tsconfig.json b/addons/xterm-addon-webgl/src/tsconfig.json index 34149159..7e53b20d 100644 --- a/addons/xterm-addon-webgl/src/tsconfig.json +++ b/addons/xterm-addon-webgl/src/tsconfig.json @@ -14,7 +14,8 @@ "paths": { "common/*": [ "../../../src/common/*" ], "browser/*": [ "../../../src/browser/*" ] - } + }, + "strictNullChecks": true }, "include": [ "./**/*", From bc07225394464d3883c5974d511b03ed8cbd0a91 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 4 Jul 2019 09:08:56 -0700 Subject: [PATCH 29/30] Enable strict mode in webgl addon Fixes #2286 --- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 5 ++++- addons/xterm-addon-webgl/src/RectangleRenderer.ts | 4 ++-- addons/xterm-addon-webgl/src/WebglRenderer.ts | 2 +- addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts | 4 ++-- .../xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts | 2 +- addons/xterm-addon-webgl/src/tsconfig.json | 2 +- 6 files changed, 11 insertions(+), 8 deletions(-) diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index 4ae01698..186cb2c1 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -75,7 +75,7 @@ const BYTES_PER_CELL = INDICES_PER_CELL * Float32Array.BYTES_PER_ELEMENT; const CELL_POSITION_INDICES = 2; export class GlyphRenderer { - private _atlas: WebglCharAtlas; + private _atlas: WebglCharAtlas | undefined; private _program: WebGLProgram; private _vertexArrayObject: IWebGLVertexArrayObject; @@ -188,6 +188,9 @@ export class GlyphRenderer { } let rasterizedGlyph: IRasterizedGlyph; + if (!this._atlas) { + throw new Error('atlas must be set before updating cell'); + } if (chars && chars.length > 1) { rasterizedGlyph = this._atlas.getRasterizedGlyphCombinedChar(chars, attr, bg, fg); } else { diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index 3d755c98..2c464d2a 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -66,8 +66,8 @@ export class RectangleRenderer { private _resolutionLocation: WebGLUniformLocation; private _attributesBuffer: WebGLBuffer; private _projectionLocation: WebGLUniformLocation; - private _bgFloat: Float32Array; - private _selectionFloat: Float32Array; + private _bgFloat!: Float32Array; + private _selectionFloat!: Float32Array; private _vertices: IVertices = { count: 0, diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 93befcec..3d41e1b5 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -27,7 +27,7 @@ export const INDICIES_PER_CELL = 4; export class WebglRenderer extends Disposable implements IRenderer { private _renderLayers: IRenderLayer[]; - private _charAtlas: WebglCharAtlas; + private _charAtlas: WebglCharAtlas | undefined; private _devicePixelRatio: number; private _model: RenderModel = new RenderModel(); diff --git a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts index 5385e658..df0cdbab 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts @@ -19,7 +19,7 @@ import { throwIfFalsy } from '../WebglUtils'; export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; - protected _ctx: CanvasRenderingContext2D; + protected _ctx!: CanvasRenderingContext2D; private _scaledCharWidth: number = 0; private _scaledCharHeight: number = 0; private _scaledCellWidth: number = 0; @@ -27,7 +27,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { private _scaledCharLeft: number = 0; private _scaledCharTop: number = 0; - protected _charAtlas: WebglCharAtlas; + protected _charAtlas: WebglCharAtlas | undefined; /** * An object that's reused when drawing glyphs in order to reduce GC. diff --git a/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts index d0a6aa82..6aed5f53 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts @@ -26,7 +26,7 @@ const BLINK_INTERVAL = 600; export class CursorRenderLayer extends BaseRenderLayer { private _state: ICursorState; private _cursorRenderers: {[key: string]: (terminal: Terminal, x: number, y: number, cell: ICellData) => void}; - private _cursorBlinkStateManager: CursorBlinkStateManager; + private _cursorBlinkStateManager: CursorBlinkStateManager | undefined; private _cell: ICellData = new CellData(); constructor(container: HTMLElement, zIndex: number, colors: IColorSet) { diff --git a/addons/xterm-addon-webgl/src/tsconfig.json b/addons/xterm-addon-webgl/src/tsconfig.json index 7e53b20d..00134015 100644 --- a/addons/xterm-addon-webgl/src/tsconfig.json +++ b/addons/xterm-addon-webgl/src/tsconfig.json @@ -15,7 +15,7 @@ "common/*": [ "../../../src/common/*" ], "browser/*": [ "../../../src/browser/*" ] }, - "strictNullChecks": true + "strict": true }, "include": [ "./**/*", From 47164d2f68b8c9ebb3043fb54f0fabcfb7005aae Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 4 Jul 2019 12:31:21 -0700 Subject: [PATCH 30/30] Move sound into browser --- src/Terminal.ts | 10 +++++----- src/Types.d.ts | 4 ---- src/browser/services/Services.d.ts | 4 ++++ .../services/SoundService.ts} | 17 +++++++++-------- 4 files changed, 18 insertions(+), 17 deletions(-) rename src/{SoundManager.ts => browser/services/SoundService.ts} (74%) diff --git a/src/Terminal.ts b/src/Terminal.ts index f8360117..902c6d51 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -34,7 +34,7 @@ import { SelectionService } from './browser/services/SelectionService'; import * as Browser from 'common/Platform'; import { addDisposableDomListener } from 'browser/Lifecycle'; import * as Strings from './browser/LocalizableStrings'; -import { SoundManager } from './SoundManager'; +import { SoundService } from 'browser/services/SoundService'; import { MouseZoneManager } from './MouseZoneManager'; import { AccessibilityManager } from './AccessibilityManager'; import { ITheme, IMarker, IDisposable, ISelectionPosition } from 'xterm'; @@ -49,7 +49,7 @@ import { ColorManager } from 'browser/ColorManager'; import { RenderService } from 'browser/services/RenderService'; import { IOptionsService, IBufferService, ICoreService } from 'common/services/Services'; import { OptionsService } from 'common/services/OptionsService'; -import { ICharSizeService, IRenderService, IMouseService, ISelectionService } from 'browser/services/Services'; +import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ISoundService } from 'browser/services/Services'; import { CharSizeService } from 'browser/services/CharSizeService'; import { BufferService, MINIMUM_COLS, MINIMUM_ROWS } from 'common/services/BufferService'; import { Disposable } from 'common/Lifecycle'; @@ -116,6 +116,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp private _mouseService: IMouseService; private _renderService: IRenderService; private _selectionService: ISelectionService; + private _soundService: ISoundService; // modes public applicationKeypad: boolean; @@ -174,7 +175,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp private _userScrolling: boolean; private _inputHandler: InputHandler; - public soundManager: SoundManager; public linkifier: ILinkifier; public viewport: IViewport; private _compositionHelper: ICompositionHelper; @@ -304,7 +304,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._selectionService = this._selectionService || null; this.linkifier = this.linkifier || new Linkifier(this); this._mouseZoneManager = this._mouseZoneManager || null; - this.soundManager = this.soundManager || new SoundManager(this); if (this.options.windowsMode) { this._windowsMode = applyWindowsMode(this); @@ -619,6 +618,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._renderService.onRender(e => this._onRender.fire(e)); this.onResize(e => this._renderService.resize(e.cols, e.rows)); + this._soundService = new SoundService(this.optionsService); this._mouseService = new MouseService(this._renderService, this._charSizeService); this._mouseZoneManager = new MouseZoneManager(this, this._mouseService); @@ -1676,7 +1676,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp */ public bell(): void { if (this._soundBell()) { - this.soundManager.playBellSound(); + this._soundService.playBellSound(); } if (this._visualBell()) { diff --git a/src/Types.d.ts b/src/Types.d.ts index eaf6196b..a54606a8 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -346,10 +346,6 @@ export interface IBrowser { isWindows: boolean; } -export interface ISoundManager { - playBellSound(): void; -} - export interface IMouseZoneManager extends IDisposable { add(zone: IMouseZone): void; clearAll(start?: number, end?: number): void; diff --git a/src/browser/services/Services.d.ts b/src/browser/services/Services.d.ts index b22a0b37..603d4109 100644 --- a/src/browser/services/Services.d.ts +++ b/src/browser/services/Services.d.ts @@ -72,3 +72,7 @@ export interface ISelectionService { refresh(isLinuxMouseSelection?: boolean): void; onMouseDown(event: MouseEvent): void; } + +export interface ISoundService { + playBellSound(): void; +} diff --git a/src/SoundManager.ts b/src/browser/services/SoundService.ts similarity index 74% rename from src/SoundManager.ts rename to src/browser/services/SoundService.ts index 6bff444f..31380031 100644 --- a/src/SoundManager.ts +++ b/src/browser/services/SoundService.ts @@ -3,35 +3,36 @@ * @license MIT */ -import { ITerminal, ISoundManager } from './Types'; +import { IOptionsService } from 'common/services/Services'; +import { ISoundService } from 'browser/services/Services'; -export class SoundManager implements ISoundManager { +export class SoundService implements ISoundService { private static _audioContext: AudioContext; static get audioContext(): AudioContext | null { - if (!SoundManager._audioContext) { + if (!SoundService._audioContext) { const audioContextCtor: typeof AudioContext = (window).AudioContext || (window).webkitAudioContext; if (!audioContextCtor) { console.warn('Web Audio API is not supported by this browser. Consider upgrading to the latest version'); return null; } - SoundManager._audioContext = new audioContextCtor(); + SoundService._audioContext = new audioContextCtor(); } - return SoundManager._audioContext; + return SoundService._audioContext; } constructor( - private _terminal: ITerminal + private _optionsService: IOptionsService ) { } public playBellSound(): void { - const ctx = SoundManager.audioContext; + const ctx = SoundService.audioContext; if (!ctx) { return; } const bellAudioSource = ctx.createBufferSource(); - ctx.decodeAudioData(this._base64ToArrayBuffer(this._removeMimeType(this._terminal.options.bellSound)), (buffer) => { + ctx.decodeAudioData(this._base64ToArrayBuffer(this._removeMimeType(this._optionsService.options.bellSound)), (buffer) => { bellAudioSource.buffer = buffer; bellAudioSource.connect(ctx.destination); bellAudioSource.start(0);