Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support typed array in parser #1796

Merged
merged 32 commits into from
Jan 27, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
06e8954
typed array support for parser
jerch Nov 23, 2018
14d6960
Merge remote-tracking branch 'upstream/master' into typedarray_parser
jerch Nov 23, 2018
591dfd5
fix DCS handler
jerch Nov 29, 2018
a4e3646
Merge branch 'master' into typedarray_parser
jerch Nov 29, 2018
a6ef64d
always clear DCS buffer on HOOK
jerch Nov 29, 2018
5d3ef75
Merge branch 'master' into typedarray_parser
jerch Nov 29, 2018
2483a98
use faster string conversion
jerch Nov 29, 2018
ef171bf
Merge branch 'master' into typedarray_parser
jerch Dec 15, 2018
d5bffb5
Merge branch 'master' into typedarray_parser
Tyriar Dec 27, 2018
570cd78
Merge branch 'master' into typedarray_parser
jerch Jan 2, 2019
83c734e
Merge branch 'typedarray_parser' of git+ssh://github.com/jerch/xterm.…
jerch Jan 2, 2019
5fd764a
dont pullin everything from .vscode folder
jerch Jan 2, 2019
dfc853a
add TextDecoder for string to UTF32
jerch Jan 2, 2019
2918a67
fix wrongly changed test case
jerch Jan 2, 2019
8fc66a3
switch parse buffer to UTF32
jerch Jan 2, 2019
1fb1f55
Merge branch 'master' into typedarray_parser
jerch Jan 2, 2019
27b91f6
speedup utf32ToString conversion
jerch Jan 3, 2019
9bcf06f
Merge branch 'master' into typedarray_parser
jerch Jan 5, 2019
e7e0f67
Merge branch 'master' into typedarray_parser
jerch Jan 12, 2019
a1f8749
Merge branch 'typedarray_parser' of git+ssh://github.com/jerch/xterm.…
jerch Jan 12, 2019
329d40a
cleanup DCS handler
jerch Jan 12, 2019
9549896
Merge branch 'master' into typedarray_parser
jerch Jan 17, 2019
19931e7
Merge branch 'master' into typedarray_parser
jerch Jan 25, 2019
21165a4
Merge branch 'master' into typedarray_parser
jerch Jan 25, 2019
4855b60
move utf32ToString to TextDecoder.ts
jerch Jan 25, 2019
87e9789
more explicit docs for DCS parsers
jerch Jan 25, 2019
90a41a1
review changes
jerch Jan 27, 2019
8e37883
Merge branch 'master' into typedarray_parser
Tyriar Jan 27, 2019
e67bef6
cleanup types
jerch Jan 27, 2019
fdf784e
Merge branch 'master' into typedarray_parser
jerch Jan 27, 2019
975dfc4
Merge branch 'typedarray_parser' of git+ssh://github.com/jerch/xterm.…
jerch Jan 27, 2019
358d70d
cleanup gitignore
jerch Jan 27, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
321 changes: 171 additions & 150 deletions src/EscapeSequenceParser.test.ts

Large diffs are not rendered by default.

29 changes: 15 additions & 14 deletions src/EscapeSequenceParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import { ParserState, ParserAction, IParsingState, IDcsHandler, IEscapeSequenceParser } from './Types';
import { IDisposable } from 'xterm';
import { Disposable } from './common/Lifecycle';
import { utf32ToString } from './core/input/TextDecoder';

interface IHandlerCollection<T> {
[key: string]: T[];
Expand Down Expand Up @@ -134,6 +135,7 @@ export const VT500_TRANSITION_TABLE = (function (): TransitionTable {
table.addMany(PRINTABLES, ParserState.SOS_PM_APC_STRING, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING);
table.addMany(EXECUTABLES, ParserState.SOS_PM_APC_STRING, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING);
table.add(0x9c, ParserState.SOS_PM_APC_STRING, ParserAction.IGNORE, ParserState.GROUND);
table.add(0x7f, ParserState.SOS_PM_APC_STRING, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING);
// csi entries
table.add(0x5b, ParserState.ESCAPE, ParserAction.CLEAR, ParserState.CSI_ENTRY);
table.addMany(r(0x40, 0x7f), ParserState.CSI_ENTRY, ParserAction.CSI_DISPATCH, ParserState.GROUND);
Expand Down Expand Up @@ -202,7 +204,7 @@ export const VT500_TRANSITION_TABLE = (function (): TransitionTable {
*/
class DcsDummy implements IDcsHandler {
hook(collect: string, params: number[], flag: number): void { }
put(data: string, start: number, end: number): void { }
put(data: Uint32Array, start: number, end: number): void { }
unhook(): void { }
}

Expand All @@ -228,7 +230,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
protected _collect: string;

// handler lookup containers
protected _printHandler: (data: string, start: number, end: number) => void;
protected _printHandler: (data: Uint32Array, start: number, end: number) => void;
protected _executeHandlers: any;
protected _csiHandlers: IHandlerCollection<CsiHandler>;
protected _escHandlers: any;
Expand All @@ -238,7 +240,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
protected _errorHandler: (state: IParsingState) => IParsingState;

// fallback handlers
protected _printHandlerFb: (data: string, start: number, end: number) => void;
protected _printHandlerFb: (data: Uint32Array, start: number, end: number) => void;
protected _executeHandlerFb: (code: number) => void;
protected _csiHandlerFb: (collect: string, params: number[], flag: number) => void;
protected _escHandlerFb: (collect: string, flag: number) => void;
Expand Down Expand Up @@ -294,7 +296,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
this._errorHandler = null;
}

setPrintHandler(callback: (data: string, start: number, end: number) => void): void {
setPrintHandler(callback: (data: Uint32Array, start: number, end: number) => void): void {
this._printHandler = callback;
}
clearPrintHandler(): void {
Expand Down Expand Up @@ -397,7 +399,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
this._activeDcsHandler = null;
}

parse(data: string): void {
parse(data: Uint32Array, length: number): void {
let code = 0;
let transition = 0;
let error = false;
Expand All @@ -412,15 +414,14 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
let callback: Function | null = null;

// process input string
const l = data.length;
for (let i = 0; i < l; ++i) {
code = data.charCodeAt(i);
for (let i = 0; i < length; ++i) {
code = data[i];

// shortcut for most chars (print action)
if (currentState === ParserState.GROUND && code > 0x1f && code < 0x80) {
print = (~print) ? print : i;
do i++;
while (i < l && data.charCodeAt(i) > 0x1f && data.charCodeAt(i) < 0x80);
while (i < length && data[i] > 0x1f && data[i] < 0x80);
i--;
continue;
}
Expand Down Expand Up @@ -563,10 +564,10 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
break;
case ParserAction.OSC_PUT:
for (let j = i + 1; ; j++) {
if (j >= l
|| (code = data.charCodeAt(j)) < 0x20
if (j >= length
|| (code = data[j]) < 0x20
|| (code > 0x7f && code <= 0x9f)) {
osc += data.substring(i, j);
osc += utf32ToString(data, i, j);
i = j - 1;
break;
}
Expand Down Expand Up @@ -610,9 +611,9 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP

// push leftover pushable buffers to terminal
if (currentState === ParserState.GROUND && ~print) {
this._printHandler(data, print, data.length);
this._printHandler(data, print, length);
} else if (currentState === ParserState.DCS_PASSTHROUGH && ~dcs && dcsHandler) {
dcsHandler.put(data, dcs, data.length);
dcsHandler.put(data, dcs, length);
}

// save non pushable buffers
Expand Down
4 changes: 3 additions & 1 deletion src/InputHandler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,9 @@ describe('InputHandler', () => {
it('should not cause an infinite loop (regression test)', () => {
const term = new Terminal();
const inputHandler = new InputHandler(term);
inputHandler.print(String.fromCharCode(0x200B), 0, 1);
const container = new Uint32Array(10);
container[0] = 0x200B;
inputHandler.print(container, 0, 1);
});
});

Expand Down
94 changes: 42 additions & 52 deletions src/InputHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { EscapeSequenceParser } from './EscapeSequenceParser';
import { ICharset } from './core/Types';
import { IDisposable } from 'xterm';
import { Disposable } from './common/Lifecycle';
import { concat } from './common/TypedArrayUtils';
import { StringToUtf32, stringFromCodePoint, utf32ToString } from './core/input/TextDecoder';

/**
* Map collect to glevel. Used in `selectCharset`.
Expand All @@ -32,21 +34,22 @@ const GLEVEL: {[key: string]: number} = {'(': 0, ')': 1, '*': 2, '+': 3, '-': 1,
* Response: DECRPSS (https://vt100.net/docs/vt510-rm/DECRPSS.html)
*/
class DECRQSS implements IDcsHandler {
private _data: string;
private _data: Uint32Array = new Uint32Array(0);

constructor(private _terminal: any) { }

hook(collect: string, params: number[], flag: number): void {
// reset data
this._data = '';
this._data = new Uint32Array(0);
}

put(data: string, start: number, end: number): void {
this._data += data.substring(start, end);
put(data: Uint32Array, start: number, end: number): void {
this._data = concat(this._data, data.subarray(start, end));
}

unhook(): void {
switch (this._data) {
const data = utf32ToString(this._data);
this._data = new Uint32Array(0);
switch (data) {
// valid: DCS 1 $ r Pt ST (xterm)
case '"q': // DECSCA
return this._terminal.handler(`${C0.ESC}P1$r0"q${C0.ESC}\\`);
Expand All @@ -66,7 +69,7 @@ class DECRQSS implements IDcsHandler {
return this._terminal.handler(`${C0.ESC}P1$r${style} q${C0.ESC}\\`);
default:
// invalid: DCS 0 $ r Pt ST (xterm)
this._terminal.error('Unknown DCS $q %s', this._data);
this._terminal.error('Unknown DCS $q %s', data);
this._terminal.handler(`${C0.ESC}P0$r${C0.ESC}\\`);
}
}
Expand All @@ -78,11 +81,17 @@ class DECRQSS implements IDcsHandler {
* not supported
*/

/**
* DCS + p Pt ST (xterm)
* Set Terminfo Data
* not supported
*/
/**
* DCS + q Pt ST (xterm)
* Request Terminfo String
* not implemented
*/

/**
* DCS + p Pt ST (xterm)
* Set Terminfo Data
* not supported
*/



Expand All @@ -94,7 +103,8 @@ class DECRQSS implements IDcsHandler {
* each function's header comment.
*/
export class InputHandler extends Disposable implements IInputHandler {
private _surrogateFirst: string;
private _parseBuffer: Uint32Array = new Uint32Array(4096);
private _stringDecoder: StringToUtf32 = new StringToUtf32();

constructor(
protected _terminal: IInputHandlingTerminal,
Expand All @@ -104,8 +114,6 @@ export class InputHandler extends Disposable implements IInputHandler {

this.register(this._parser);

this._surrogateFirst = '';

/**
* custom fallback handlers
*/
Expand Down Expand Up @@ -290,23 +298,23 @@ export class InputHandler extends Disposable implements IInputHandler {
this._terminal.log('data: ' + data);
}

// apply leftover surrogate high from last write
if (this._surrogateFirst) {
data = this._surrogateFirst + data;
this._surrogateFirst = '';
if (this._parseBuffer.length < data.length) {
this._parseBuffer = new Uint32Array(data.length);
}

this._parser.parse(data);
for (let i = 0; i < data.length; ++i) {
this._parseBuffer[i] = data.charCodeAt(i);
}
this._parser.parse(this._parseBuffer, this._stringDecoder.decode(data, this._parseBuffer));

buffer = this._terminal.buffer;
if (buffer.x !== cursorStartX || buffer.y !== cursorStartY) {
this._terminal.emit('cursormove');
}
}

public print(data: string, start: number, end: number): void {
let char: string;
public print(data: Uint32Array, start: number, end: number): void {
let code: number;
let char: string;
let chWidth: number;
const buffer: IBuffer = this._terminal.buffer;
const charset: ICharset = this._terminal.charset;
Expand All @@ -318,41 +326,23 @@ export class InputHandler extends Disposable implements IInputHandler {
let bufferRow = buffer.lines.get(buffer.y + buffer.ybase);

this._terminal.updateRange(buffer.y);
for (let stringPosition = start; stringPosition < end; ++stringPosition) {
char = data.charAt(stringPosition);
code = data.charCodeAt(stringPosition);

// surrogate pair handling
if (0xD800 <= code && code <= 0xDBFF) {
if (++stringPosition >= end) {
// end of input:
// handle pairs as true UTF-16 and wait for the second part
// since we expect the input comming from a stream there is
// a small chance that the surrogate pair got split
// therefore we dont process the first char here, instead
// it gets added as first char to the next processed chunk
this._surrogateFirst = char;
continue;
}
const second = data.charCodeAt(stringPosition);
// if the second part is in surrogate pair range create the high codepoint
// otherwise fall back to UCS-2 behavior (handle codepoints independently)
if (0xDC00 <= second && second <= 0xDFFF) {
code = (code - 0xD800) * 0x400 + second - 0xDC00 + 0x10000;
char += data.charAt(stringPosition);
} else {
stringPosition--;
}
}
for (let pos = start; pos < end; ++pos) {
code = data[pos];
char = stringFromCodePoint(code);

// calculate print space
// expensive call, therefore we save width in line buffer
chWidth = wcwidth(code);

// get charset replacement character
if (charset) {
char = charset[char] || char;
code = char.charCodeAt(0);
// charset are only defined for ASCII, therefore we only
// search for an replacement char if code < 127
if (code < 127 && charset) {
const ch = charset[char];
if (ch) {
code = ch.charCodeAt(0);
char = ch;
}
}

if (screenReaderMode) {
Expand Down
24 changes: 16 additions & 8 deletions src/Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export interface ICompositionHelper {
*/
export interface IInputHandler {
parse(data: string): void;
print(data: string, start: number, end: number): void;
print(data: Uint32Array, start: number, end: number): void;

/** C0 BEL */ bell(): void;
/** C0 LF */ lineFeed(): void;
Expand Down Expand Up @@ -452,18 +452,26 @@ export interface IParsingState {
* DCS handler signature for EscapeSequenceParser.
* EscapeSequenceParser handles DCS commands via separate
* subparsers that get hook/unhooked and can handle
* arbitrary amount of print data.
* arbitrary amount of data.
*
* On entering a DSC sequence `hook` is called by
* `EscapeSequenceParser`. Use it to initialize or reset
* states needed to handle the current DCS sequence.
* Note: A DCS parser is only instantiated once, therefore
* you cannot rely on the ctor to reinitialize state.
*
* EscapeSequenceParser will call `put` several times if the
* parsed string got splitted, therefore you might have to collect
* `data` until `unhook` is called. `unhook` marks the end
* of the current DCS sequence.
* parsed data got split, therefore you might have to collect
* `data` until `unhook` is called.
* Note: `data` is borrowed, if you cannot process the data
* in chunks you have to copy it, doing otherwise will lead to
* data losses or corruption.
*
* `unhook` marks the end of the current DCS sequence.
*/
export interface IDcsHandler {
hook(collect: string, params: number[], flag: number): void;
put(data: string, start: number, end: number): void;
put(data: Uint32Array, start: number, end: number): void;
unhook(): void;
}

Expand All @@ -480,9 +488,9 @@ export interface IEscapeSequenceParser extends IDisposable {
* Parse string `data`.
* @param data The data to parse.
*/
parse(data: string): void;
parse(data: Uint32Array, length: number): void;

setPrintHandler(callback: (data: string, start: number, end: number) => void): void;
setPrintHandler(callback: (data: Uint32Array, start: number, end: number) => void): void;
clearPrintHandler(): void;

setExecuteHandler(flag: string, callback: () => void): void;
Expand Down
24 changes: 16 additions & 8 deletions src/common/TypedArrayUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,20 @@
* @license MIT
*/
import { assert } from 'chai';
import { fillFallback } from './TypedArrayUtils';
import { fillFallback, concat } from './TypedArrayUtils';

type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray
| Int8Array | Int16Array | Int32Array
| Float32Array | Float64Array;

describe('polyfill conformance tests', function(): void {

function deepEquals(a: TypedArray, b: TypedArray): void {
assert.equal(a.length, b.length);
for (let i = 0; i < a.length; ++i) {
assert.equal(a[i], b[i]);
}
function deepEquals(a: TypedArray, b: TypedArray): void {
assert.equal(a.length, b.length);
for (let i = 0; i < a.length; ++i) {
assert.equal(a[i], b[i]);
}
}

describe('polyfill conformance tests', function(): void {
describe('TypedArray.fill', function(): void {
it('should work with all typed array types', function(): void {
const u81 = new Uint8Array(5);
Expand Down Expand Up @@ -87,3 +86,12 @@ describe('polyfill conformance tests', function(): void {
});
});
});

describe('typed array convenience functions', () => {
it('concat', () => {
const a = new Uint8Array([1, 2, 3, 4, 5]);
const b = new Uint8Array([6, 7, 8, 9, 0]);
const merged = concat(a, b);
deepEquals(merged, new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]));
});
});
Loading