register functions by prefix/intermediate/final bytes

This commit is contained in:
Jörg Breitbart
2019-07-31 04:49:01 +02:00
parent d78c062169
commit 812fc5e6ec
14 changed files with 652 additions and 539 deletions
+9 -11
View File
@@ -53,39 +53,38 @@ describe('InputHandler', () => {
it('should call Terminal.setOption with correct params', () => {
const optionsService = new MockOptionsService();
const inputHandler = new InputHandler(new MockInputHandlingTerminal(), new MockBufferService(80, 30), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), optionsService);
const collect = ' ';
inputHandler.setCursorStyle(Params.fromArray([0]), collect);
inputHandler.setCursorStyle(Params.fromArray([0]));
assert.equal(optionsService.options['cursorStyle'], 'block');
assert.equal(optionsService.options['cursorBlink'], true);
optionsService.options = clone(DEFAULT_OPTIONS);
inputHandler.setCursorStyle(Params.fromArray([1]), collect);
inputHandler.setCursorStyle(Params.fromArray([1]));
assert.equal(optionsService.options['cursorStyle'], 'block');
assert.equal(optionsService.options['cursorBlink'], true);
optionsService.options = clone(DEFAULT_OPTIONS);
inputHandler.setCursorStyle(Params.fromArray([2]), collect);
inputHandler.setCursorStyle(Params.fromArray([2]));
assert.equal(optionsService.options['cursorStyle'], 'block');
assert.equal(optionsService.options['cursorBlink'], false);
optionsService.options = clone(DEFAULT_OPTIONS);
inputHandler.setCursorStyle(Params.fromArray([3]), collect);
inputHandler.setCursorStyle(Params.fromArray([3]));
assert.equal(optionsService.options['cursorStyle'], 'underline');
assert.equal(optionsService.options['cursorBlink'], true);
optionsService.options = clone(DEFAULT_OPTIONS);
inputHandler.setCursorStyle(Params.fromArray([4]), collect);
inputHandler.setCursorStyle(Params.fromArray([4]));
assert.equal(optionsService.options['cursorStyle'], 'underline');
assert.equal(optionsService.options['cursorBlink'], false);
optionsService.options = clone(DEFAULT_OPTIONS);
inputHandler.setCursorStyle(Params.fromArray([5]), collect);
inputHandler.setCursorStyle(Params.fromArray([5]));
assert.equal(optionsService.options['cursorStyle'], 'bar');
assert.equal(optionsService.options['cursorBlink'], true);
optionsService.options = clone(DEFAULT_OPTIONS);
inputHandler.setCursorStyle(Params.fromArray([6]), collect);
inputHandler.setCursorStyle(Params.fromArray([6]));
assert.equal(optionsService.options['cursorStyle'], 'bar');
assert.equal(optionsService.options['cursorBlink'], false);
});
@@ -93,14 +92,13 @@ describe('InputHandler', () => {
describe('setMode', () => {
it('should toggle Terminal.bracketedPasteMode', () => {
const terminal = new MockInputHandlingTerminal();
const collect = '?';
terminal.bracketedPasteMode = false;
const inputHandler = new InputHandler(terminal, new MockBufferService(80, 30), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService());
// Set bracketed paste mode
inputHandler.setMode(Params.fromArray([2004]), collect);
inputHandler.setModePrivate(Params.fromArray([2004]));
assert.equal(terminal.bracketedPasteMode, true);
// Reset bracketed paste mode
inputHandler.resetMode(Params.fromArray([2004]), collect);
inputHandler.resetModePrivate(Params.fromArray([2004]));
assert.equal(terminal.bracketedPasteMode, false);
});
});
+204 -213
View File
File diff suppressed because it is too large Load Diff
+7 -7
View File
@@ -55,7 +55,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';
import { IParams, IFunctionIdentifier } from 'common/parser/Types';
import { CoreService } from 'common/services/CoreService';
import { LogService } from 'common/services/LogService';
import { ILinkifier, IMouseZoneManager, LinkMatcherHandler, ILinkMatcherOptions, IViewport } from 'browser/Types';
@@ -1395,18 +1395,18 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
}
/** Add handler for ESC escape sequence. See xterm.d.ts for details. */
public addEscHandler(collectAndFlag: string, handler: () => boolean): IDisposable {
return this._inputHandler.addEscHandler(collectAndFlag, handler);
public addEscHandler(id: IFunctionIdentifier, callback: () => boolean): IDisposable {
return this._inputHandler.addEscHandler(id, callback);
}
/** Add handler for DCS escape sequence. See xterm.d.ts for details. */
public addDcsHandler(collectAndFlag: string, callback: (param: IParams, data: string) => boolean): IDisposable {
return this._inputHandler.addDcsHandler(collectAndFlag, callback);
public addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean): IDisposable {
return this._inputHandler.addDcsHandler(id, callback);
}
/** Add handler for CSI escape sequence. See xterm.d.ts for details. */
public addCsiHandler(flag: string, callback: (params: IParams, collect: string) => boolean): IDisposable {
return this._inputHandler.addCsiHandler(flag, callback);
public addCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean): IDisposable {
return this._inputHandler.addCsiHandler(id, callback);
}
/** Add handler for OSC escape sequence. See xterm.d.ts for details. */
public addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
+4 -4
View File
@@ -15,7 +15,7 @@ import { AttributeData } from 'common/buffer/AttributeData';
import { IColorManager, IColorSet, ILinkMatcherOptions, ILinkifier, IViewport } from 'browser/Types';
import { IOptionsService } from 'common/services/Services';
import { EventEmitter } from 'common/EventEmitter';
import { IParams } from 'common/parser/Types';
import { IParams, IFunctionIdentifier } from 'common/parser/Types';
import { ISelectionService } from 'browser/services/Services';
export class TestTerminal extends Terminal {
@@ -74,13 +74,13 @@ export class MockTerminal implements ITerminal {
attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void {
throw new Error('Method not implemented.');
}
addCsiHandler(flag: string, callback: (params: IParams, collect: string) => boolean): IDisposable {
addCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean): IDisposable {
throw new Error('Method not implemented.');
}
addDcsHandler(collectAndFlag: string, callback: (param: IParams, data: string) => boolean): IDisposable {
addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean): IDisposable {
throw new Error('Method not implemented.');
}
addEscHandler(collectAndFlag: string, handler: () => boolean): IDisposable {
addEscHandler(id: IFunctionIdentifier, handler: () => boolean): IDisposable {
throw new Error('Method not implemented.');
}
addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
+6 -5
View File
@@ -9,7 +9,7 @@ import { IEvent, IEventEmitter } from 'common/EventEmitter';
import { IColorSet, ILinkifier, ILinkMatcherOptions, IViewport } from 'browser/Types';
import { IOptionsService } from 'common/services/Services';
import { IBuffer, IBufferSet } from 'common/buffer/Types';
import { IParams } from 'common/parser/Types';
import { IParams, IFunctionIdentifier } from 'common/parser/Types';
export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean;
@@ -113,7 +113,8 @@ export interface IInputHandler {
/** 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 c */ sendDeviceAttributesPrimary(params: IParams): void;
sendDeviceAttributesSecondary(params: IParams): void;
/** CSI d */ linePosAbsolute(params: IParams): void;
/** CSI e */ vPositionRelative(params: IParams): void;
/** CSI f */ hVPosition(params: IParams): void;
@@ -200,9 +201,9 @@ export interface IPublicTerminal extends IDisposable {
writeln(data: string): void;
open(parent: HTMLElement): void;
attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void;
addCsiHandler(flag: string, callback: (params: IParams, collect: string) => boolean): IDisposable;
addDcsHandler(collectAndFlag: string, callback: (param: IParams, data: string) => boolean): IDisposable;
addEscHandler(collectAndFlag: string, handler: () => boolean): IDisposable;
addCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean): IDisposable;
addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean): IDisposable;
addEscHandler(id: IFunctionIdentifier, callback: () => 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;
+81 -44
View File
@@ -4,7 +4,7 @@
*/
import { assert } from 'chai';
import { DcsParser, DcsHandlerFactory } from 'common/parser/DcsParser';
import { IDcsHandler, IParams } from 'common/parser/Types';
import { IDcsHandler, IParams, IFunctionIdentifier } from 'common/parser/Types';
import { utf32ToString, StringToUtf32 } from 'common/input/TextDecoder';
import { Params } from 'common/parser/Params';
import { PAYLOAD_LIMIT } from 'common/parser/Constants';
@@ -16,10 +16,47 @@ function toUtf32(s: string): Uint32Array {
return utf32.subarray(0, length);
}
function identifier(id: IFunctionIdentifier): number {
let res = 0;
if (id.prefix) {
if (id.prefix.length > 1) {
throw new Error('only one byte as prefix supported');
}
res = id.prefix.charCodeAt(0);
if (res && 0x3c > res || res > 0x3f) {
throw new Error('prefix must be in range 0x3c .. 0x3f');
}
}
if (id.intermediates) {
if (id.intermediates.length > 2) {
throw new Error('only two bytes as intermediates are supported');
}
for (let i = 0; i < id.intermediates.length; ++i) {
const intermediate = id.intermediates.charCodeAt(i);
if (0x20 > intermediate || intermediate > 0x2f) {
throw new Error('intermediate must be in range 0x20 .. 0x2f');
}
res <<= 8;
res |= intermediate;
}
}
if (id.final.length !== 1) {
throw new Error('final must be a single byte');
}
const finalCode = id.final.charCodeAt(0);
if (0x40 > finalCode || finalCode > 0x7e) {
throw new Error('final must be in range 0x40 .. 0x7e');
}
res <<= 8;
res |= finalCode;
return res;
}
class TestHandler implements IDcsHandler {
constructor(public output: any[], public msg: string, public returnFalse: boolean = false) {}
hook(collect: string, params: IParams, flag: number): void {
this.output.push([this.msg, 'HOOK', params.toArray(), collect, flag]);
hook(params: IParams): void {
this.output.push([this.msg, 'HOOK', params.toArray()]);
}
put(data: Uint32Array, start: number, end: number): void {
this.output.push([this.msg, 'PUT', utf32ToString(data, start, end)]);
@@ -39,16 +76,16 @@ describe('DcsParser', () => {
reports = [];
parser = new DcsParser();
parser.setDcsHandlerFallback((id, action, data) => {
if (data.params) {
data.params = data.params.toArray();
if (action === 'HOOK') {
data = data.toArray();
}
reports.push([id, action, data]);
});
});
describe('handler registration', () => {
it('setDcsHandler', () => {
parser.setDcsHandler('+p', new TestHandler(reports, 'th'));
parser.hook('+', Params.fromArray([1, 2, 3]), 'p'.charCodeAt(0));
parser.setDcsHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th'));
parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));
let data = toUtf32('Here comes');
parser.put(data, 0, data.length);
data = toUtf32('the mouse!');
@@ -56,16 +93,16 @@ describe('DcsParser', () => {
parser.unhook(true);
assert.deepEqual(reports, [
// messages from TestHandler
['th', 'HOOK', [1, 2, 3], '+', 'p'.charCodeAt(0)],
['th', 'HOOK', [1, 2, 3]],
['th', 'PUT', 'Here comes'],
['th', 'PUT', 'the mouse!'],
['th', 'UNHOOK', true]
]);
});
it('clearDcsHandler', () => {
parser.setDcsHandler('+p', new TestHandler(reports, 'th'));
parser.clearDcsHandler('+p');
parser.hook('+', Params.fromArray([1, 2, 3]), 'p'.charCodeAt(0));
parser.setDcsHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th'));
parser.clearDcsHandler(identifier({intermediates: '+', final: 'p'}));
parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));
let data = toUtf32('Here comes');
parser.put(data, 0, data.length);
data = toUtf32('the mouse!');
@@ -73,24 +110,24 @@ describe('DcsParser', () => {
parser.unhook(true);
assert.deepEqual(reports, [
// messages from fallback handler
['+p', 'HOOK', {collect: '+', params: [1, 2, 3], flag: 'p'.charCodeAt(0)}],
['+p', 'PUT', 'Here comes'],
['+p', 'PUT', 'the mouse!'],
['+p', 'UNHOOK', true]
[identifier({intermediates: '+', final: 'p'}), 'HOOK', [1, 2, 3]],
[identifier({intermediates: '+', final: 'p'}), 'PUT', 'Here comes'],
[identifier({intermediates: '+', final: 'p'}), 'PUT', 'the mouse!'],
[identifier({intermediates: '+', final: 'p'}), 'UNHOOK', true]
]);
});
it('addDcsHandler', () => {
parser.setDcsHandler('+p', new TestHandler(reports, 'th1'));
parser.addDcsHandler('+p', new TestHandler(reports, 'th2'));
parser.hook('+', Params.fromArray([1, 2, 3]), 'p'.charCodeAt(0));
parser.setDcsHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th1'));
parser.addDcsHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th2'));
parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));
let data = toUtf32('Here comes');
parser.put(data, 0, data.length);
data = toUtf32('the mouse!');
parser.put(data, 0, data.length);
parser.unhook(true);
assert.deepEqual(reports, [
['th2', 'HOOK', [1, 2, 3], '+', 'p'.charCodeAt(0)],
['th1', 'HOOK', [1, 2, 3], '+', 'p'.charCodeAt(0)],
['th2', 'HOOK', [1, 2, 3]],
['th1', 'HOOK', [1, 2, 3]],
['th2', 'PUT', 'Here comes'],
['th1', 'PUT', 'Here comes'],
['th2', 'PUT', 'the mouse!'],
@@ -100,17 +137,17 @@ describe('DcsParser', () => {
]);
});
it('addDcsHandler with return false', () => {
parser.setDcsHandler('+p', new TestHandler(reports, 'th1'));
parser.addDcsHandler('+p', new TestHandler(reports, 'th2', true));
parser.hook('+', Params.fromArray([1, 2, 3]), 'p'.charCodeAt(0));
parser.setDcsHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th1'));
parser.addDcsHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th2', true));
parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));
let data = toUtf32('Here comes');
parser.put(data, 0, data.length);
data = toUtf32('the mouse!');
parser.put(data, 0, data.length);
parser.unhook(true);
assert.deepEqual(reports, [
['th2', 'HOOK', [1, 2, 3], '+', 'p'.charCodeAt(0)],
['th1', 'HOOK', [1, 2, 3], '+', 'p'.charCodeAt(0)],
['th2', 'HOOK', [1, 2, 3]],
['th1', 'HOOK', [1, 2, 3]],
['th2', 'PUT', 'Here comes'],
['th1', 'PUT', 'Here comes'],
['th2', 'PUT', 'the mouse!'],
@@ -120,17 +157,17 @@ describe('DcsParser', () => {
]);
});
it('dispose handlers', () => {
parser.setDcsHandler('+p', new TestHandler(reports, 'th1'));
const dispo = parser.addDcsHandler('+p', new TestHandler(reports, 'th2', true));
parser.setDcsHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th1'));
const dispo = parser.addDcsHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 'th2', true));
dispo.dispose();
parser.hook('+', Params.fromArray([1, 2, 3]), 'p'.charCodeAt(0));
parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));
let data = toUtf32('Here comes');
parser.put(data, 0, data.length);
data = toUtf32('the mouse!');
parser.put(data, 0, data.length);
parser.unhook(true);
assert.deepEqual(reports, [
['th1', 'HOOK', [1, 2, 3], '+', 'p'.charCodeAt(0)],
['th1', 'HOOK', [1, 2, 3]],
['th1', 'PUT', 'Here comes'],
['th1', 'PUT', 'the mouse!'],
['th1', 'UNHOOK', true]
@@ -139,8 +176,8 @@ describe('DcsParser', () => {
});
describe('DcsHandlerFactory', () => {
it('should be called once on end(true)', () => {
parser.setDcsHandler('+p', new DcsHandlerFactory((params, data) => reports.push([params.toArray(), data])));
parser.hook('+', Params.fromArray([1, 2, 3]), 'p'.charCodeAt(0));
parser.setDcsHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandlerFactory((data, params) => reports.push([params.toArray(), data])));
parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));
let data = toUtf32('Here comes');
parser.put(data, 0, data.length);
data = toUtf32(' the mouse!');
@@ -149,8 +186,8 @@ describe('DcsParser', () => {
assert.deepEqual(reports, [[[1, 2, 3], 'Here comes the mouse!']]);
});
it('should not be called on end(false)', () => {
parser.setDcsHandler('+p', new DcsHandlerFactory((params, data) => reports.push([params.toArray(), data])));
parser.hook('+', Params.fromArray([1, 2, 3]), 'p'.charCodeAt(0));
parser.setDcsHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandlerFactory((data, params) => reports.push([params.toArray(), data])));
parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));
let data = toUtf32('Here comes');
parser.put(data, 0, data.length);
data = toUtf32(' the mouse!');
@@ -159,9 +196,9 @@ describe('DcsParser', () => {
assert.deepEqual(reports, []);
});
it('should be disposable', () => {
parser.setDcsHandler('+p', new DcsHandlerFactory((params, data) => reports.push(['one', params.toArray(), data])));
const dispo = parser.addDcsHandler('+p', new DcsHandlerFactory((params, data) => reports.push(['two', params.toArray(), data])));
parser.hook('+', Params.fromArray([1, 2, 3]), 'p'.charCodeAt(0));
parser.setDcsHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandlerFactory((data, params) => reports.push(['one', params.toArray(), data])));
const dispo = parser.addDcsHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandlerFactory((data, params) => reports.push(['two', params.toArray(), data])));
parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));
let data = toUtf32('Here comes');
parser.put(data, 0, data.length);
data = toUtf32(' the mouse!');
@@ -169,7 +206,7 @@ describe('DcsParser', () => {
parser.unhook(true);
assert.deepEqual(reports, [['two', [1, 2, 3], 'Here comes the mouse!']]);
dispo.dispose();
parser.hook('+', Params.fromArray([1, 2, 3]), 'p'.charCodeAt(0));
parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));
data = toUtf32('some other');
parser.put(data, 0, data.length);
data = toUtf32(' data');
@@ -178,9 +215,9 @@ describe('DcsParser', () => {
assert.deepEqual(reports, [['two', [1, 2, 3], 'Here comes the mouse!'], ['one', [1, 2, 3], 'some other data']]);
});
it('should respect return false', () => {
parser.setDcsHandler('+p', new DcsHandlerFactory((params, data) => reports.push(['one', params.toArray(), data])));
parser.addDcsHandler('+p', new DcsHandlerFactory((params, data) => { reports.push(['two', params.toArray(), data]); return false; }));
parser.hook('+', Params.fromArray([1, 2, 3]), 'p'.charCodeAt(0));
parser.setDcsHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandlerFactory((data, params) => reports.push(['one', params.toArray(), data])));
parser.addDcsHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandlerFactory((data, params) => { reports.push(['two', params.toArray(), data]); return false; }));
parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));
let data = toUtf32('Here comes');
parser.put(data, 0, data.length);
data = toUtf32(' the mouse!');
@@ -190,8 +227,8 @@ describe('DcsParser', () => {
});
it('should work up to payload limit', function(): void {
this.timeout(10000);
parser.setDcsHandler('+p', new DcsHandlerFactory((params, data) => reports.push([params.toArray(), data])));
parser.hook('+', Params.fromArray([1, 2, 3]), 'p'.charCodeAt(0));
parser.setDcsHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandlerFactory((data, params) => reports.push([params.toArray(), data])));
parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));
const data = toUtf32('A'.repeat(1000));
for (let i = 0; i < PAYLOAD_LIMIT; i += 1000) {
parser.put(data, 0, data.length);
@@ -201,8 +238,8 @@ describe('DcsParser', () => {
});
it('should abort for payload limit +1', function(): void {
this.timeout(10000);
parser.setDcsHandler('+p', new DcsHandlerFactory((params, data) => reports.push([params.toArray(), data])));
parser.hook('+', Params.fromArray([1, 2, 3]), 'p'.charCodeAt(0));
parser.setDcsHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandlerFactory((data, params) => reports.push([params.toArray(), data])));
parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));
let data = toUtf32('A'.repeat(1000));
for (let i = 0; i < PAYLOAD_LIMIT; i += 1000) {
parser.put(data, 0, data.length);
+22 -22
View File
@@ -4,7 +4,7 @@
*/
import { IDisposable } from 'common/Types';
import { IDcsHandler, IParams, ParamsArray, IHandlerCollection, IDcsParser, DcsFallbackHandler } from 'common/parser/Types';
import { IDcsHandler, IParams, IHandlerCollection, IDcsParser, DcsFallbackHandler } from 'common/parser/Types';
import { utf32ToString } from 'common/input/TextDecoder';
import { Params } from 'common/parser/Params';
import { PAYLOAD_LIMIT } from 'common/parser/Constants';
@@ -13,7 +13,7 @@ import { PAYLOAD_LIMIT } from 'common/parser/Constants';
export class DcsParser implements IDcsParser {
private _handlers: IHandlerCollection<IDcsHandler> = Object.create(null);
private _active: IDcsHandler[] = [];
private _collectAndFlag: string = '';
private _ident: number = 0;
private _handlerFb: DcsFallbackHandler = () => {};
public dispose(): void {
@@ -21,11 +21,11 @@ export class DcsParser implements IDcsParser {
this._handlerFb = () => {};
}
public addDcsHandler(collectAndFlag: string, handler: IDcsHandler): IDisposable {
if (this._handlers[collectAndFlag] === undefined) {
this._handlers[collectAndFlag] = [];
public addDcsHandler(ident: number, handler: IDcsHandler): IDisposable {
if (this._handlers[ident] === undefined) {
this._handlers[ident] = [];
}
const handlerList = this._handlers[collectAndFlag];
const handlerList = this._handlers[ident];
handlerList.push(handler);
return {
dispose: () => {
@@ -37,12 +37,12 @@ export class DcsParser implements IDcsParser {
};
}
public setDcsHandler(collectAndFlag: string, handler: IDcsHandler): void {
this._handlers[collectAndFlag] = [handler];
public setDcsHandler(ident: number, handler: IDcsHandler): void {
this._handlers[ident] = [handler];
}
public clearDcsHandler(collectAndFlag: string): void {
if (this._handlers[collectAndFlag]) delete this._handlers[collectAndFlag];
public clearDcsHandler(ident: number): void {
if (this._handlers[ident]) delete this._handlers[ident];
}
public setDcsHandlerFallback(handler: DcsFallbackHandler): void {
@@ -54,24 +54,24 @@ export class DcsParser implements IDcsParser {
this.unhook(false);
}
this._active = [];
this._collectAndFlag = '';
this._ident = 0;
}
public hook(collect: string, params: IParams, flag: number): void {
this._collectAndFlag = collect + String.fromCharCode(flag);
this._active = this._handlers[this._collectAndFlag] || [];
public hook(ident: number, params: IParams): void {
this._ident = ident;
this._active = this._handlers[ident] || [];
if (!this._active.length) {
this._handlerFb(this._collectAndFlag, 'HOOK', {collect, params, flag});
this._handlerFb(this._ident, 'HOOK', params);
} else {
for (let j = this._active.length - 1; j >= 0; j--) {
this._active[j].hook(collect, params, flag);
this._active[j].hook(params);
}
}
}
public put(data: Uint32Array, start: number, end: number): void {
if (!this._active.length) {
this._handlerFb(this._collectAndFlag, 'PUT', utf32ToString(data, start, end));
this._handlerFb(this._ident, 'PUT', utf32ToString(data, start, end));
} else {
for (let j = this._active.length - 1; j >= 0; j--) {
this._active[j].put(data, start, end);
@@ -81,7 +81,7 @@ export class DcsParser implements IDcsParser {
public unhook(success: boolean): void {
if (!this._active.length) {
this._handlerFb(this._collectAndFlag, 'UNHOOK', success);
this._handlerFb(this._ident, 'UNHOOK', success);
} else {
let j = this._active.length - 1;
for (; j >= 0; j--) {
@@ -96,7 +96,7 @@ export class DcsParser implements IDcsParser {
}
}
this._active = [];
this._collectAndFlag = '';
this._ident = 0;
}
}
@@ -109,9 +109,9 @@ export class DcsHandlerFactory implements IDcsHandler {
private _params: IParams | undefined;
private _hitLimit: boolean = false;
constructor(private _handler: (params: IParams, data: string) => any) {}
constructor(private _handler: (data: string, params: IParams) => any) {}
public hook(collect: string, params: IParams, flag: number): void {
public hook(params: IParams): void {
this._params = params.clone();
this._data = '';
this._hitLimit = false;
@@ -133,7 +133,7 @@ export class DcsHandlerFactory implements IDcsHandler {
if (this._hitLimit) {
ret = false;
} else if (success) {
ret = this._handler(this._params ? this._params : new Params(), this._data);
ret = this._handler(this._data, this._params ? this._params : new Params());
}
this._params = undefined;
this._data = '';
+90 -85
View File
@@ -73,10 +73,14 @@ class TestEscapeSequenceParser extends EscapeSequenceParser {
return this._params;
}
public get collect(): string {
return this._collect;
return this.identToString(this._collect);
}
public set collect(value: string) {
this._collect = value;
this._collect = 0;
for (let i = 0; i < value.length; ++i) {
this._collect <<= 8;
this._collect |= value.charCodeAt(i);
}
}
public mockOscParser(): void {
this._oscParser = oscPutParser;
@@ -111,8 +115,8 @@ const testTerminal: any = {
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(), flag]);
actionDCSHook: function (params: IParams): void {
this.calls.push(['dcs hook', params.toArray()]);
},
actionDCSPrint: function (s: string): void {
this.calls.push(['dcs put', s]);
@@ -144,11 +148,13 @@ let state: any;
const testParser = new TestEscapeSequenceParser();
testParser.mockOscParser();
testParser.setPrintHandler(testTerminal.print.bind(testTerminal));
testParser.setCsiHandlerFallback((collect: string, params: IParams, flag: number) => {
testTerminal.actionCSI(collect, params, String.fromCharCode(flag));
testParser.setCsiHandlerFallback((ident: number, params: IParams) => {
const id = testParser.identToString(ident);
testTerminal.actionCSI(id.slice(0, -1), params, id.slice(-1));
});
testParser.setEscHandlerFallback((collect: string, flag: number) => {
testTerminal.actionESC(collect, String.fromCharCode(flag));
testParser.setEscHandlerFallback((ident: number) => {
const id = testParser.identToString(ident);
testTerminal.actionESC(id.slice(0, -1), id.slice(-1));
});
testParser.setExecuteHandlerFallback((code: number) => {
testTerminal.actionExecute(String.fromCharCode(code));
@@ -160,7 +166,7 @@ testParser.setOscHandlerFallback((identifier, action, data) => {
testParser.setDcsHandlerFallback((collectAndFlag, action, payload) => {
switch (action) {
case 'HOOK':
testTerminal.actionDCSHook(payload.collect, payload.params, String.fromCharCode(payload.flag));
testTerminal.actionDCSHook(payload);
break;
case 'PUT':
testTerminal.actionDCSPrint(payload);
@@ -939,7 +945,7 @@ describe('EscapeSequenceParser', function (): void {
parser.currentState = ParserState.DCS_ENTRY;
parse(parser, collect[i]);
chai.expect(parser.currentState).equal(ParserState.DCS_PASSTHROUGH);
testTerminal.compare([['dcs hook', '', [0], collect[i]]]);
testTerminal.compare([['dcs hook', [0]]]);
parser.reset();
testTerminal.clear();
}
@@ -952,7 +958,7 @@ describe('EscapeSequenceParser', function (): void {
parser.currentState = ParserState.DCS_PARAM;
parse(parser, collect[i]);
chai.expect(parser.currentState).equal(ParserState.DCS_PASSTHROUGH);
testTerminal.compare([['dcs hook', '', [0], collect[i]]]);
testTerminal.compare([['dcs hook', [0]]]);
parser.reset();
testTerminal.clear();
}
@@ -965,7 +971,7 @@ describe('EscapeSequenceParser', function (): void {
parser.currentState = ParserState.DCS_INTERMEDIATE;
parse(parser, collect[i]);
chai.expect(parser.currentState).equal(ParserState.DCS_PASSTHROUGH);
testTerminal.compare([['dcs hook', '', [0], collect[i]]]);
testTerminal.compare([['dcs hook', [0]]]);
parser.reset();
testTerminal.clear();
}
@@ -1024,14 +1030,14 @@ describe('EscapeSequenceParser', function (): void {
});
it('single DCS', function (): void {
test('\x1bP1;2;3+$aäbc;däe\x9c', [
['dcs hook', '+$', [1, 2, 3], 'a'],
['dcs hook', [1, 2, 3]],
['dcs put', 'äbc;däe'],
['dcs unhook']
], null);
});
it('multi DCS', function (): void {
test('\x1bP1;2;3+$abc;de', [
['dcs hook', '+$', [1, 2, 3], 'a'],
['dcs hook', [1, 2, 3]],
['dcs put', 'bc;de']
], null);
testTerminal.clear();
@@ -1043,7 +1049,7 @@ describe('EscapeSequenceParser', function (): void {
it('print + DCS(C1)', function (): void {
test('abc\x901;2;3+$abc;de\x9c', [
['print', 'abc'],
['dcs hook', '+$', [1, 2, 3], 'a'],
['dcs hook', [1, 2, 3]],
['dcs put', 'bc;de'],
['dcs unhook']
], null);
@@ -1086,7 +1092,7 @@ describe('EscapeSequenceParser', function (): void {
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 hook', [1, 2, [-1, 55], 3]],
['dcs put', 'bc;de'],
['dcs unhook']
], null);
@@ -1120,7 +1126,7 @@ describe('EscapeSequenceParser', function (): void {
parser.currentState = ParserState.DCS_PASSTHROUGH;
parse(parser, '\x901;2;3+$a€öäü');
chai.expect(parser.currentState).equal(ParserState.DCS_PASSTHROUGH);
testTerminal.compare([['dcs hook', '+$', [1, 2, 3], 'a'], ['dcs put', '€öäü']]);
testTerminal.compare([['dcs hook', [1, 2, 3]], ['dcs put', '€öäü']]);
parser.reset();
testTerminal.clear();
});
@@ -1172,69 +1178,69 @@ describe('EscapeSequenceParser', function (): void {
chai.expect(print).equal('');
});
it('ESC handler', function (): void {
parser2.setEscHandler('%G', function (): void {
parser2.setEscHandler({intermediates: '%', final: 'G'}, function (): void {
esc.push('%G');
});
parser2.setEscHandler('E', function (): void {
parser2.setEscHandler({final: 'E'}, function (): void {
esc.push('E');
});
parse(parser2, INPUT);
chai.expect(esc).eql(['%G', 'E']);
parser2.clearEscHandler('%G');
parser2.clearEscHandler('%G'); // should not throw
parser2.clearEscHandler({intermediates: '%', final: 'G'});
parser2.clearEscHandler({intermediates: '%', final: 'G'}); // should not throw
clearAccu();
parse(parser2, INPUT);
chai.expect(esc).eql(['E']);
parser2.clearEscHandler('E');
parser2.clearEscHandler({final: 'E'});
clearAccu();
parse(parser2, INPUT);
chai.expect(esc).eql([]);
});
describe('ESC custom handlers', () => {
it('prevent fallback', () => {
parser2.setEscHandler('%G', () => esc.push('default - %G'));
parser2.addEscHandler('%G', () => { esc.push('custom - %G'); return true; });
parser2.setEscHandler({intermediates: '%', final: 'G'}, () => esc.push('default - %G'));
parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom - %G'); return true; });
parse(parser2, INPUT);
chai.expect(esc).eql(['custom - %G']);
});
it('allow fallback', () => {
parser2.setEscHandler('%G', () => esc.push('default - %G'));
parser2.addEscHandler('%G', () => { esc.push('custom - %G'); return false; });
parser2.setEscHandler({intermediates: '%', final: 'G'}, () => esc.push('default - %G'));
parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom - %G'); return false; });
parse(parser2, INPUT);
chai.expect(esc).eql(['custom - %G', 'default - %G']);
});
it('Multiple custom handlers fallback once', () => {
parser2.setEscHandler('%G', () => esc.push('default - %G'));
parser2.addEscHandler('%G', () => { esc.push('custom - %G'); return true; });
parser2.addEscHandler('%G', () => { esc.push('custom2 - %G'); return false; });
parser2.setEscHandler({intermediates: '%', final: 'G'}, () => esc.push('default - %G'));
parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom - %G'); return true; });
parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom2 - %G'); return false; });
parse(parser2, INPUT);
chai.expect(esc).eql(['custom2 - %G', 'custom - %G']);
});
it('Multiple custom handlers no fallback', () => {
parser2.setEscHandler('%G', () => esc.push('default - %G'));
parser2.addEscHandler('%G', () => { esc.push('custom - %G'); return true; });
parser2.addEscHandler('%G', () => { esc.push('custom2 - %G'); return true; });
parser2.setEscHandler({intermediates: '%', final: 'G'}, () => esc.push('default - %G'));
parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom - %G'); return true; });
parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom2 - %G'); return true; });
parse(parser2, INPUT);
chai.expect(esc).eql(['custom2 - %G']);
});
it('Execution order should go from latest handler down to the original', () => {
const order: number[] = [];
parser2.setEscHandler('%G', () => order.push(1));
parser2.addEscHandler('%G', () => { order.push(2); return false; });
parser2.addEscHandler('%G', () => { order.push(3); return false; });
parser2.setEscHandler({intermediates: '%', final: 'G'}, () => order.push(1));
parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { order.push(2); return false; });
parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { order.push(3); return false; });
parse(parser2, '\x1b%G');
chai.expect(order).eql([3, 2, 1]);
});
it('Dispose should work', () => {
parser2.setEscHandler('%G', () => esc.push('default - %G'));
const dispo = parser2.addEscHandler('%G', () => { esc.push('custom - %G'); return true; });
parser2.setEscHandler({intermediates: '%', final: 'G'}, () => esc.push('default - %G'));
const dispo = parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom - %G'); return true; });
dispo.dispose();
parse(parser2, INPUT);
chai.expect(esc).eql(['default - %G']);
});
it('Should not corrupt the parser when dispose is called twice', () => {
parser2.setEscHandler('%G', () => esc.push('default - %G'));
const dispo = parser2.addEscHandler('%G', () => { esc.push('custom - %G'); return true; });
parser2.setEscHandler({intermediates: '%', final: 'G'}, () => esc.push('default - %G'));
const dispo = parser2.addEscHandler({intermediates: '%', final: 'G'}, () => { esc.push('custom - %G'); return true; });
dispo.dispose();
dispo.dispose();
parse(parser2, INPUT);
@@ -1242,13 +1248,13 @@ describe('EscapeSequenceParser', function (): void {
});
});
it('CSI handler', function (): void {
parser2.setCsiHandler('m', function (params: IParams, collect: string): void {
csi.push(['m', params.toArray(), collect]);
parser2.setCsiHandler({final: 'm'}, function (params: IParams): void {
csi.push(['m', params.toArray(), '']);
});
parse(parser2, INPUT);
chai.expect(csi).eql([['m', [1, 31], ''], ['m', [0], '']]);
parser2.clearCsiHandler('m');
parser2.clearCsiHandler('m'); // should not throw
parser2.clearCsiHandler({final: 'm'});
parser2.clearCsiHandler({final: 'm'}); // should not throw
clearAccu();
parse(parser2, INPUT);
chai.expect(csi).eql([]);
@@ -1256,16 +1262,16 @@ describe('EscapeSequenceParser', function (): void {
describe('CSI custom handlers', () => {
it('Prevent fallback', () => {
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; });
parser2.setCsiHandler({final: 'm'}, params => csi.push(['m', params.toArray(), '']));
parser2.addCsiHandler({final: 'm'}, params => { csiCustom.push(['m', params.toArray(), '']); 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, 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; });
parser2.setCsiHandler({final: 'm'}, params => csi.push(['m', params.toArray(), '']));
parser2.addCsiHandler({final: 'm'}, params => { csiCustom.push(['m', params.toArray(), '']); 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], '']]);
@@ -1273,9 +1279,9 @@ describe('EscapeSequenceParser', function (): void {
it('Multiple custom handlers fallback once', () => {
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; });
parser2.setCsiHandler({final: 'm'}, params => csi.push(['m', params.toArray(), '']));
parser2.addCsiHandler({final: 'm'}, params => { csiCustom.push(['m', params.toArray(), '']); return true; });
parser2.addCsiHandler({final: 'm'}, params => { csiCustom2.push(['m', params.toArray(), '']); return false; });
parse(parser2, INPUT);
chai.expect(csi).eql([], 'Should not fallback to original handler');
chai.expect(csiCustom).eql([['m', [1, 31], ''], ['m', [0], '']]);
@@ -1284,9 +1290,9 @@ describe('EscapeSequenceParser', function (): void {
it('Multiple custom handlers no fallback', () => {
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; });
parser2.setCsiHandler({final: 'm'}, params => csi.push(['m', params.toArray(), '']));
parser2.addCsiHandler({final: 'm'}, params => { csiCustom.push(['m', params.toArray(), '']); return true; });
parser2.addCsiHandler({final: 'm'}, params => { csiCustom2.push(['m', params.toArray(), '']); return true; });
parse(parser2, INPUT);
chai.expect(csi).eql([], 'Should not fallback to original handler');
chai.expect(csiCustom).eql([], 'Should not fallback once');
@@ -1294,16 +1300,16 @@ describe('EscapeSequenceParser', function (): void {
});
it('Execution order should go from latest handler down to the original', () => {
const order: number[] = [];
parser2.setCsiHandler('m', () => order.push(1));
parser2.addCsiHandler('m', () => { order.push(2); return false; });
parser2.addCsiHandler('m', () => { order.push(3); return false; });
parser2.setCsiHandler({final: 'm'}, () => order.push(1));
parser2.addCsiHandler({final: 'm'}, () => { order.push(2); return false; });
parser2.addCsiHandler({final: 'm'}, () => { order.push(3); return false; });
parse(parser2, '\x1b[0m');
chai.expect(order).eql([3, 2, 1]);
});
it('Dispose should work', () => {
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; });
parser2.setCsiHandler({final: 'm'}, params => csi.push(['m', params.toArray(), '']));
const customHandler = parser2.addCsiHandler({final: 'm'}, params => { csiCustom.push(['m', params.toArray(), '']); return true; });
customHandler.dispose();
parse(parser2, INPUT);
chai.expect(csi).eql([['m', [1, 31], ''], ['m', [0], '']]);
@@ -1311,8 +1317,8 @@ describe('EscapeSequenceParser', function (): void {
});
it('Should not corrupt the parser when dispose is called twice', () => {
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; });
parser2.setCsiHandler({final: 'm'}, params => csi.push(['m', params.toArray(), '']));
const customHandler = parser2.addCsiHandler({final: 'm'}, params => { csiCustom.push(['m', params.toArray(), '']); return true; });
customHandler.dispose();
customHandler.dispose();
parse(parser2, INPUT);
@@ -1415,9 +1421,9 @@ 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(), flag]);
parser2.setDcsHandler({intermediates: '+', final: 'p'}, {
hook: function (params: IParams): void {
dcs.push(['hook', '', params.toArray(), 0]);
},
put: function (data: Uint32Array, start: number, end: number): void {
let s = '';
@@ -1433,12 +1439,12 @@ describe('EscapeSequenceParser', function (): void {
parse(parser2, '\x1bP1;2;3+pabc');
parse(parser2, ';de\x9c');
chai.expect(dcs).eql([
['hook', '+', [1, 2, 3], 'p'.charCodeAt(0)],
['hook', '', [1, 2, 3], 0],
['put', 'abc'], ['put', ';de'],
['unhook']
]);
parser2.clearDcsHandler('+p');
parser2.clearDcsHandler('+p'); // should not throw
parser2.clearDcsHandler({intermediates: '+', final: 'p'});
parser2.clearDcsHandler({intermediates: '+', final: 'p'}); // should not throw
clearAccu();
parse(parser2, '\x1bP1;2;3+pabc');
parse(parser2, ';de\x9c');
@@ -1448,54 +1454,54 @@ describe('EscapeSequenceParser', function (): void {
const DCS_INPUT = '\x1bP1;2;3+pabc\x1b\\';
it('Prevent fallback', () => {
const dcsCustom: [string, (number | number[])[], string][] = [];
parser2.setDcsHandler('+p', new DcsHandlerFactory((params, data) => dcsCustom.push(['A', params.toArray(), data])));
parser2.addDcsHandler('+p', new DcsHandlerFactory((params, data) => { dcsCustom.push(['B', params.toArray(), data]); return true; }));
parser2.setDcsHandler({intermediates: '+', final: 'p'}, new DcsHandlerFactory((data, params) => dcsCustom.push(['A', params.toArray(), data])));
parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandlerFactory((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return true; }));
parse(parser2, DCS_INPUT);
chai.expect(dcsCustom).eql([['B', [1, 2, 3], 'abc']]);
});
it('Allow fallback', () => {
const dcsCustom: [string, (number | number[])[], string][] = [];
parser2.setDcsHandler('+p', new DcsHandlerFactory((params, data) => dcsCustom.push(['A', params.toArray(), data])));
parser2.addDcsHandler('+p', new DcsHandlerFactory((params, data) => { dcsCustom.push(['B', params.toArray(), data]); return false; }));
parser2.setDcsHandler({intermediates: '+', final: 'p'}, new DcsHandlerFactory((data, params) => dcsCustom.push(['A', params.toArray(), data])));
parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandlerFactory((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return false; }));
parse(parser2, DCS_INPUT);
chai.expect(dcsCustom).eql([['B', [1, 2, 3], 'abc'], ['A', [1, 2, 3], 'abc']]);
});
it('Multiple custom handlers fallback once', () => {
const dcsCustom: [string, (number | number[])[], string][] = [];
parser2.setDcsHandler('+p', new DcsHandlerFactory((params, data) => dcsCustom.push(['A', params.toArray(), data])));
parser2.addDcsHandler('+p', new DcsHandlerFactory((params, data) => { dcsCustom.push(['B', params.toArray(), data]); return true; }));
parser2.addDcsHandler('+p', new DcsHandlerFactory((params, data) => { dcsCustom.push(['C', params.toArray(), data]); return false; }));
parser2.setDcsHandler({intermediates: '+', final: 'p'}, new DcsHandlerFactory((data, params) => dcsCustom.push(['A', params.toArray(), data])));
parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandlerFactory((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return true; }));
parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandlerFactory((data, params) => { dcsCustom.push(['C', params.toArray(), data]); return false; }));
parse(parser2, DCS_INPUT);
chai.expect(dcsCustom).eql([['C', [1, 2, 3], 'abc'], ['B', [1, 2, 3], 'abc']]);
});
it('Multiple custom handlers no fallback', () => {
const dcsCustom: [string, (number | number[])[], string][] = [];
parser2.setDcsHandler('+p', new DcsHandlerFactory((params, data) => dcsCustom.push(['A', params.toArray(), data])));
parser2.addDcsHandler('+p', new DcsHandlerFactory((params, data) => { dcsCustom.push(['B', params.toArray(), data]); return true; }));
parser2.addDcsHandler('+p', new DcsHandlerFactory((params, data) => { dcsCustom.push(['C', params.toArray(), data]); return true; }));
parser2.setDcsHandler({intermediates: '+', final: 'p'}, new DcsHandlerFactory((data, params) => dcsCustom.push(['A', params.toArray(), data])));
parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandlerFactory((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return true; }));
parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandlerFactory((data, params) => { dcsCustom.push(['C', params.toArray(), data]); return true; }));
parse(parser2, DCS_INPUT);
chai.expect(dcsCustom).eql([['C', [1, 2, 3], 'abc']]);
});
it('Execution order should go from latest handler down to the original', () => {
const order: number[] = [];
parser2.setDcsHandler('+p', new DcsHandlerFactory(() => order.push(1)));
parser2.addDcsHandler('+p', new DcsHandlerFactory(() => { order.push(2); return false; }));
parser2.addDcsHandler('+p', new DcsHandlerFactory(() => { order.push(3); return false; }));
parser2.setDcsHandler({intermediates: '+', final: 'p'}, new DcsHandlerFactory(() => order.push(1)));
parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandlerFactory(() => { order.push(2); return false; }));
parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandlerFactory(() => { order.push(3); return false; }));
parse(parser2, DCS_INPUT);
chai.expect(order).eql([3, 2, 1]);
});
it('Dispose should work', () => {
const dcsCustom: [string, (number | number[])[], string][] = [];
parser2.setDcsHandler('+p', new DcsHandlerFactory((params, data) => dcsCustom.push(['A', params.toArray(), data])));
const dispo = parser2.addDcsHandler('+p', new DcsHandlerFactory((params, data) => { dcsCustom.push(['B', params.toArray(), data]); return true; }));
parser2.setDcsHandler({intermediates: '+', final: 'p'}, new DcsHandlerFactory((data, params) => dcsCustom.push(['A', params.toArray(), data])));
const dispo = parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandlerFactory((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return true; }));
dispo.dispose();
parse(parser2, DCS_INPUT);
chai.expect(dcsCustom).eql([['A', [1, 2, 3], 'abc']]);
});
it('Should not corrupt the parser when dispose is called twice', () => {
const dcsCustom: [string, (number | number[])[], string][] = [];
parser2.setDcsHandler('+p', new DcsHandlerFactory((params, data) => dcsCustom.push(['A', params.toArray(), data])));
const dispo = parser2.addDcsHandler('+p', new DcsHandlerFactory((params, data) => { dcsCustom.push(['B', params.toArray(), data]); return true; }));
parser2.setDcsHandler({intermediates: '+', final: 'p'}, new DcsHandlerFactory((data, params) => dcsCustom.push(['A', params.toArray(), data])));
const dispo = parser2.addDcsHandler({intermediates: '+', final: 'p'}, new DcsHandlerFactory((data, params) => { dcsCustom.push(['B', params.toArray(), data]); return true; }));
dispo.dispose();
dispo.dispose();
parse(parser2, DCS_INPUT);
@@ -1513,8 +1519,7 @@ describe('EscapeSequenceParser', function (): void {
position: 6,
code: '€'.charCodeAt(0),
currentState: ParserState.CSI_PARAM,
osc: '',
collect: '',
collect: 0,
params: Params.fromArray([1, 2, 0]), // extra zero here
abort: false
});
+104 -49
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { IParsingState, IDcsHandler, IEscapeSequenceParser, IParams, IOscHandler, IHandlerCollection, CsiHandler, OscFallbackHandler, IOscParser, EscHandler, IDcsParser, DcsFallbackHandler } from 'common/parser/Types';
import { IParsingState, IDcsHandler, IEscapeSequenceParser, IParams, IOscHandler, IHandlerCollection, CsiHandler, OscFallbackHandler, IOscParser, EscHandler, IDcsParser, DcsFallbackHandler, IFunctionIdentifier } from 'common/parser/Types';
import { ParserState, ParserAction } from 'common/parser/Constants';
import { Disposable } from 'common/Lifecycle';
import { IDisposable } from 'common/Types';
@@ -206,17 +206,25 @@ export const VT500_TRANSITION_TABLE = (function (): TransitionTable {
* 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
* the optional `transitions` constructor argument and
* reimplement the `parse` method.
*
* 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
* This this is not in line with the latest ECMA-48 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.
* are set to -1 (no ZDM for sub parameters).
*
* About prefix and intermediate bytes:
* This parser follows the assumptions of the vt100.net parser with these restrictions:
* - only one prefix byte is allowed as first parameter byte, byte range 0x3c .. 0x3f
* - max. two intermediates are respected, byte range 0x20 .. 0x2f
* Note that this is not in line with ECMA-48 which does not limit either of those.
* Furthermore ECMA-48 allows the prefix byte range at any param byte position. Currently
* there are no known sequences that follow the broader definition of the specification.
*
* TODO: implement error recovery hook via error handler return values
*/
@@ -227,7 +235,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
// buffers over several parse calls
protected _params: Params;
protected _collect: string;
protected _collect: number;
// handler lookup containers
protected _printHandler: (data: Uint32Array, start: number, end: number) => void;
@@ -241,8 +249,8 @@ 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: IParams, flag: number) => void;
protected _escHandlerFb: (collect: string, flag: number) => void;
protected _csiHandlerFb: (ident: number, params: IParams) => void;
protected _escHandlerFb: (ident: number) => void;
protected _errorHandlerFb: (state: IParsingState) => IParsingState;
constructor(readonly TRANSITIONS: TransitionTable = VT500_TRANSITION_TABLE) {
@@ -252,14 +260,14 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
this.currentState = this.initialState;
this._params = new Params(); // defaults to 32 storable params/subparams
this._params.addParam(0); // ZDM
this._collect = '';
this._collect = 0;
this.precedingCodepoint = 0;
// set default fallback handlers and handler lookup containers
this._printHandlerFb = (data, start, end): void => { };
this._executeHandlerFb = (code: number): void => { };
this._csiHandlerFb = (collect: string, params: IParams, flag: number): void => { };
this._escHandlerFb = (collect: string, flag: number): void => { };
this._csiHandlerFb = (ident: number, params: IParams): void => { };
this._escHandlerFb = (ident: number): void => { };
this._errorHandlerFb = (state: IParsingState): IParsingState => state;
this._printHandler = this._printHandlerFb;
this._executeHandlers = Object.create(null);
@@ -270,7 +278,53 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
this._errorHandler = this._errorHandlerFb;
// swallow 7bit ST (ESC+\)
this.setEscHandler('\\', () => {});
this.setEscHandler({final: '\\'}, () => {});
}
private _identifier(id: IFunctionIdentifier, finalRange: number[] = [0x40, 0x7e]): number {
let res = 0;
if (id.prefix) {
if (id.prefix.length > 1) {
throw new Error('only one byte as prefix supported');
}
res = id.prefix.charCodeAt(0);
if (res && 0x3c > res || res > 0x3f) {
throw new Error('prefix must be in range 0x3c .. 0x3f');
}
}
if (id.intermediates) {
if (id.intermediates.length > 2) {
throw new Error('only two bytes as intermediates are supported');
}
for (let i = 0; i < id.intermediates.length; ++i) {
const intermediate = id.intermediates.charCodeAt(i);
if (0x20 > intermediate || intermediate > 0x2f) {
throw new Error('intermediate must be in range 0x20 .. 0x2f');
}
res <<= 8;
res |= intermediate;
}
}
if (id.final.length !== 1) {
throw new Error('final must be a single byte');
}
const finalCode = id.final.charCodeAt(0);
if (finalRange[0] > finalCode || finalCode > finalRange[1]) {
throw new Error(`final must be in range ${finalRange[0]} .. ${finalRange[1]}`);
}
res <<= 8;
res |= finalCode;
return res;
}
public identToString(ident: number): string {
const res: string[] = [];
while (ident) {
res.push(String.fromCharCode(ident & 0xFF));
ident >>= 8;
}
return res.reverse().join('');
}
public dispose(): void {
@@ -297,12 +351,12 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
this._executeHandlerFb = callback;
}
addCsiHandler(flag: string, callback: CsiHandler): IDisposable {
const index = flag.charCodeAt(0);
if (this._csiHandlers[index] === undefined) {
this._csiHandlers[index] = [];
addCsiHandler(id: IFunctionIdentifier, callback: CsiHandler): IDisposable {
const ident = this._identifier(id);
if (this._csiHandlers[ident] === undefined) {
this._csiHandlers[ident] = [];
}
const handlerList = this._csiHandlers[index];
const handlerList = this._csiHandlers[ident];
handlerList.push(callback);
return {
dispose: () => {
@@ -313,21 +367,22 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
}
};
}
setCsiHandler(flag: string, callback: (params: IParams, collect: string) => void): void {
this._csiHandlers[flag.charCodeAt(0)] = [callback];
setCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => void): void {
this._csiHandlers[this._identifier(id)] = [callback];
}
clearCsiHandler(flag: string): void {
if (this._csiHandlers[flag.charCodeAt(0)]) delete this._csiHandlers[flag.charCodeAt(0)];
clearCsiHandler(id: IFunctionIdentifier): void {
if (this._csiHandlers[this._identifier(id)]) delete this._csiHandlers[this._identifier(id)];
}
setCsiHandlerFallback(callback: (collect: string, params: IParams, flag: number) => void): void {
setCsiHandlerFallback(callback: (ident: number, params: IParams) => void): void {
this._csiHandlerFb = callback;
}
addEscHandler(collectAndFlag: string, callback: EscHandler): IDisposable {
if (this._escHandlers[collectAndFlag] === undefined) {
this._escHandlers[collectAndFlag] = [];
addEscHandler(id: IFunctionIdentifier, callback: EscHandler): IDisposable {
const ident = this._identifier(id, [0x30, 0x7e]);
if (this._escHandlers[ident] === undefined) {
this._escHandlers[ident] = [];
}
const handlerList = this._escHandlers[collectAndFlag];
const handlerList = this._escHandlers[ident];
handlerList.push(callback);
return {
dispose: () => {
@@ -338,13 +393,13 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
}
};
}
setEscHandler(collectAndFlag: string, callback: () => void): void {
this._escHandlers[collectAndFlag] = [callback];
setEscHandler(id: IFunctionIdentifier, callback: () => void): void {
this._escHandlers[this._identifier(id, [0x30, 0x7e])] = [callback];
}
clearEscHandler(collectAndFlag: string): void {
if (this._escHandlers[collectAndFlag]) delete this._escHandlers[collectAndFlag];
clearEscHandler(id: IFunctionIdentifier): void {
if (this._escHandlers[this._identifier(id, [0x30, 0x7e])]) delete this._escHandlers[this._identifier(id, [0x30, 0x7e])];
}
setEscHandlerFallback(callback: (collect: string, flag: number) => void): void {
setEscHandlerFallback(callback: (ident: number) => void): void {
this._escHandlerFb = callback;
}
@@ -361,14 +416,14 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
this._oscParser.setOscHandlerFallback(handler);
}
addDcsHandler(collectAndFlag: string, handler: IDcsHandler): IDisposable {
return this._dcsParser.addDcsHandler(collectAndFlag, handler);
addDcsHandler(id: IFunctionIdentifier, handler: IDcsHandler): IDisposable {
return this._dcsParser.addDcsHandler(this._identifier(id), handler);
}
setDcsHandler(collectAndFlag: string, handler: IDcsHandler): void {
this._dcsParser.setDcsHandler(collectAndFlag, handler);
setDcsHandler(id: IFunctionIdentifier, handler: IDcsHandler): void {
this._dcsParser.setDcsHandler(this._identifier(id), handler);
}
clearDcsHandler(collectAndFlag: string): void {
this._dcsParser.clearDcsHandler(collectAndFlag);
clearDcsHandler(id: IFunctionIdentifier): void {
this._dcsParser.clearDcsHandler(this._identifier(id));
}
setDcsHandlerFallback(handler: DcsFallbackHandler): void {
this._dcsParser.setDcsHandlerFallback(handler);
@@ -387,11 +442,13 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
this._dcsParser.reset();
this._params.reset();
this._params.addParam(0); // ZDM
this._collect = '';
this._collect = 0;
// this._activeDcsHandler = this._dcsHandlerFb;
this.precedingCodepoint = 0;
}
/**
* Parse UTF32 codepoints in `data` up to `length`.
*
@@ -415,7 +472,6 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
let collect = this._collect;
const params = this._params;
const table: Uint8Array = this.TRANSITIONS.table;
// let dcsHandler: IDcsHandler = this._activeDcsHandler;
let callback: Function | null = null;
// process input string
@@ -465,7 +521,6 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
position: i,
code,
currentState,
osc: '', // FIXME: what to send here?
collect,
params,
abort: false
@@ -475,16 +530,16 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
break;
case ParserAction.CSI_DISPATCH:
// Trigger CSI Handler
const handlers = this._csiHandlers[code];
const handlers = this._csiHandlers[collect << 8 | code];
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) !== false) {
break;
}
}
if (j < 0) {
this._csiHandlerFb(collect, params, code);
this._csiHandlerFb(collect << 8 | code, params);
}
this.precedingCodepoint = 0;
break;
@@ -509,10 +564,10 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
i--;
break;
case ParserAction.COLLECT:
collect += String.fromCharCode(code);
collect |= code;
break;
case ParserAction.ESC_DISPATCH:
const handlersEsc = this._escHandlers[collect + String.fromCharCode(code)];
const handlersEsc = this._escHandlers[collect << 8 | code];
let jj = handlersEsc ? handlersEsc.length - 1 : -1;
for (; jj >= 0; jj--) {
// undefined or true means success and to stop bubbling
@@ -521,7 +576,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
}
}
if (jj < 0) {
this._escHandlerFb(collect, code);
this._escHandlerFb(collect << 8 | code);
}
this.precedingCodepoint = 0;
break;
@@ -529,10 +584,10 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
osc.reset();
params.reset();
params.addParam(0); // ZDM
collect = '';
collect = 0;
break;
case ParserAction.DCS_HOOK:
dcs.hook(collect, params, code);
dcs.hook(collect << 8 | code, params);
break;
case ParserAction.DCS_PUT:
// inner loop - exit DCS_PUT: 0x18, 0x1a, 0x1b, 0x7f, 0x80 - 0x9f
@@ -551,7 +606,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
osc.reset();
params.reset();
params.addParam(0); // ZDM
collect = '';
collect = 0;
this.precedingCodepoint = 0;
break;
case ParserAction.OSC_START:
@@ -573,7 +628,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
osc.reset();
params.reset();
params.addParam(0); // ZDM
collect = '';
collect = 0;
this.precedingCodepoint = 0;
break;
}
+30 -21
View File
@@ -53,10 +53,8 @@ export interface IParsingState {
code: number;
// current parser state
currentState: ParserState;
// osc string buffer
osc: string;
// collect buffer with intermediate characters
collect: string;
collect: number;
// params buffer
params: IParams;
// should abort (default: false)
@@ -67,7 +65,7 @@ export interface IHandlerCollection<T> {
[key: string]: T[];
}
export type CsiHandler = (params: IParams, collect: string) => boolean | void;
export type CsiHandler = (params: IParams) => boolean | void;
export type EscHandler = () => boolean | void;
/**
@@ -93,12 +91,12 @@ export type EscHandler = () => boolean | void;
* indicates whether the command was aborted.
*/
export interface IDcsHandler {
hook(collect: string, params: IParams, flag: number): void;
hook(params: IParams): void;
put(data: Uint32Array, start: number, end: number): void;
unhook(success: boolean): void | boolean;
}
export type DcsFallbackHandler = (collectAndFlag: string, action: 'HOOK' | 'PUT' | 'UNHOOK', payload?: any) => void;
export type DcsFallbackHandler = (ident: number, action: 'HOOK' | 'PUT' | 'UNHOOK', payload?: any) => void;
export interface IOscHandler {
/**
@@ -145,6 +143,11 @@ export interface IEscapeSequenceParser extends IDisposable {
*/
parse(data: Uint32Array, length: number): void;
/**
* Get string from ident number.
*/
identToString(ident: number): string;
setPrintHandler(callback: (data: Uint32Array, start: number, end: number) => void): void;
clearPrintHandler(): void;
@@ -152,25 +155,25 @@ export interface IEscapeSequenceParser extends IDisposable {
clearExecuteHandler(flag: string): void;
setExecuteHandlerFallback(callback: (code: number) => void): void;
setCsiHandler(flag: string, callback: (params: IParams, collect: string) => void): void;
clearCsiHandler(flag: string): void;
setCsiHandlerFallback(callback: (collect: string, params: IParams, flag: number) => void): void;
addCsiHandler(flag: string, callback: (params: IParams, collect: string) => boolean): IDisposable;
setCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => void): void;
clearCsiHandler(id: IFunctionIdentifier): void;
setCsiHandlerFallback(callback: (identifier: number, params: IParams) => void): void;
addCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean): IDisposable;
setEscHandler(collectAndFlag: string, callback: () => void): void;
clearEscHandler(collectAndFlag: string): void;
setEscHandlerFallback(callback: (collect: string, flag: number) => void): void;
addEscHandler(collectAndFlag: string, handler: EscHandler): IDisposable;
setEscHandler(id: IFunctionIdentifier, callback: () => void): void;
clearEscHandler(id: IFunctionIdentifier): void;
setEscHandlerFallback(callback: (identifier: number) => void): void;
addEscHandler(id: IFunctionIdentifier, handler: EscHandler): IDisposable;
setOscHandler(ident: number, handler: IOscHandler): void;
clearOscHandler(ident: number): void;
setOscHandlerFallback(handler: OscFallbackHandler): void;
addOscHandler(ident: number, handler: IOscHandler): IDisposable;
setDcsHandler(collectAndFlag: string, handler: IDcsHandler): void;
clearDcsHandler(collectAndFlag: string): void;
setDcsHandler(id: IFunctionIdentifier, handler: IDcsHandler): void;
clearDcsHandler(id: IFunctionIdentifier): void;
setDcsHandlerFallback(handler: DcsFallbackHandler): void;
addDcsHandler(collectAndFlag: string, handler: IDcsHandler): IDisposable;
addDcsHandler(id: IFunctionIdentifier, handler: IDcsHandler): IDisposable;
setErrorHandler(callback: (state: IParsingState) => IParsingState): void;
clearErrorHandler(): void;
@@ -188,12 +191,18 @@ export interface IOscParser extends IDisposable {
}
export interface IDcsParser extends IDisposable {
addDcsHandler(collectAndFlag: string, handler: IDcsHandler): IDisposable;
setDcsHandler(collectAndFlag: string, handler: IDcsHandler): void;
clearDcsHandler(collectAndFlag: string): void;
addDcsHandler(ident: number, handler: IDcsHandler): IDisposable;
setDcsHandler(ident: number, handler: IDcsHandler): void;
clearDcsHandler(ident: number): void;
setDcsHandlerFallback(handler: DcsFallbackHandler): void;
reset(): void;
hook(collect: string, params: IParams, flag: number): void;
hook(ident: number, params: IParams): void;
put(data: Uint32Array, start: number, end: number): void;
unhook(success: boolean): void;
}
export interface IFunctionIdentifier {
prefix?: string;
intermediates?: string;
final: string;
}
+7 -7
View File
@@ -11,7 +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';
import { IParams, IFunctionIdentifier } from 'common/parser/Types';
export class Terminal implements ITerminalApi {
private _core: ITerminal;
@@ -57,14 +57,14 @@ export class Terminal implements ITerminalApi {
public attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void {
this._core.attachCustomKeyEventHandler(customKeyEventHandler);
}
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 addCsiHandler(id: IFunctionIdentifier, callback: (params: (number | number[])[]) => boolean): IDisposable {
return this._core.addCsiHandler(id, (params: IParams) => callback(params.toArray()));
}
public addDcsHandler(collectAndFlag: string, callback: (param: (number | number[])[], data: string) => boolean): IDisposable {
return this._core.addDcsHandler(collectAndFlag, (params: IParams, data: string) => callback(params.toArray(), data));
public addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: (number | number[])[]) => boolean): IDisposable {
return this._core.addDcsHandler(id, (data: string, params: IParams) => callback(data, params.toArray()));
}
public addEscHandler(collectAndFlag: string, handler: () => boolean): IDisposable {
return this._core.addEscHandler(collectAndFlag, handler);
public addEscHandler(id: IFunctionIdentifier, handler: () => boolean): IDisposable {
return this._core.addEscHandler(id, handler);
}
public addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
return this._core.addOscHandler(ident, callback);
+7 -7
View File
@@ -340,7 +340,7 @@ describe('InputHandler Integration Tests', function(): void {
await page.evaluate(`
window.term.reset();
const _customCsiHandlerParams = [];
const _customCsiHandler = window.term.addCsiHandler('m', (params, collect) => {
const _customCsiHandler = window.term.addCsiHandler({final: 'm'}, (params, collect) => {
_customCsiHandlerParams.push(params);
return false;
}, '');
@@ -356,15 +356,15 @@ describe('InputHandler Integration Tests', function(): void {
await page.evaluate(`
window.term.reset();
const _customDcsHandlerCallStack = [];
const _customDcsHandlerA = window.term.addDcsHandler('+p', (params, data) => {
const _customDcsHandlerA = window.term.addDcsHandler({intermediates:'+', final: 'p'}, (data, params) => {
_customDcsHandlerCallStack.push(['A', params, data]);
return false;
});
const _customDcsHandlerB = window.term.addDcsHandler('+p', (params, data) => {
const _customDcsHandlerB = window.term.addDcsHandler({intermediates:'+', final: 'p'}, (data, params) => {
_customDcsHandlerCallStack.push(['B', params, data]);
return true;
});
const _customDcsHandlerC = window.term.addDcsHandler('+p', (params, data) => {
const _customDcsHandlerC = window.term.addDcsHandler({intermediates:'+', final: 'p'}, (data, params) => {
_customDcsHandlerCallStack.push(['C', params, data]);
return false;
});
@@ -380,15 +380,15 @@ describe('InputHandler Integration Tests', function(): void {
await page.evaluate(`
window.term.reset();
const _customEscHandlerCallStack = [];
const _customEscHandlerA = window.term.addEscHandler('(B', () => {
const _customEscHandlerA = window.term.addEscHandler({intermediates:'(', final: 'B'}, () => {
_customEscHandlerCallStack.push('A');
return false;
});
const _customEscHandlerB = window.term.addEscHandler('(B', () => {
const _customEscHandlerB = window.term.addEscHandler({intermediates:'(', final: 'B'}, () => {
_customEscHandlerCallStack.push('B');
return true;
});
const _customEscHandlerC = window.term.addEscHandler('(B', () => {
const _customEscHandlerC = window.term.addEscHandler({intermediates:'(', final: 'B'}, () => {
_customEscHandlerCallStack.push('C');
return false;
});
@@ -19,7 +19,7 @@ function toUtf32(s: string): Uint32Array {
}
class DcsHandler implements IDcsHandler {
hook(collect: string, params: IParams, flag: number) : void {}
hook(params: IParams) : void {}
put(data: Uint32Array, start: number, end: number) : void {}
unhook() :void {}
}
@@ -32,42 +32,42 @@ perfContext('Parser throughput - 50MB data', () => {
beforeEach(() => {
parser = new EscapeSequenceParser();
parser.setPrintHandler((data, start, end) => {});
parser.setCsiHandler('@', (params, collect) => {});
parser.setCsiHandler('A', (params, collect) => {});
parser.setCsiHandler('B', (params, collect) => {});
parser.setCsiHandler('C', (params, collect) => {});
parser.setCsiHandler('D', (params, collect) => {});
parser.setCsiHandler('E', (params, collect) => {});
parser.setCsiHandler('F', (params, collect) => {});
parser.setCsiHandler('G', (params, collect) => {});
parser.setCsiHandler('H', (params, collect) => {});
parser.setCsiHandler('I', (params, collect) => {});
parser.setCsiHandler('J', (params, collect) => {});
parser.setCsiHandler('K', (params, collect) => {});
parser.setCsiHandler('L', (params, collect) => {});
parser.setCsiHandler('M', (params, collect) => {});
parser.setCsiHandler('P', (params, collect) => {});
parser.setCsiHandler('S', (params, collect) => {});
parser.setCsiHandler('T', (params, collect) => {});
parser.setCsiHandler('X', (params, collect) => {});
parser.setCsiHandler('Z', (params, collect) => {});
parser.setCsiHandler('`', (params, collect) => {});
parser.setCsiHandler('a', (params, collect) => {});
parser.setCsiHandler('b', (params, collect) => {});
parser.setCsiHandler('c', (params, collect) => {});
parser.setCsiHandler('d', (params, collect) => {});
parser.setCsiHandler('e', (params, collect) => {});
parser.setCsiHandler('f', (params, collect) => {});
parser.setCsiHandler('g', (params, collect) => {});
parser.setCsiHandler('h', (params, collect) => {});
parser.setCsiHandler('l', (params, collect) => {});
parser.setCsiHandler('m', (params, collect) => {});
parser.setCsiHandler('n', (params, collect) => {});
parser.setCsiHandler('p', (params, collect) => {});
parser.setCsiHandler('q', (params, collect) => {});
parser.setCsiHandler('r', (params, collect) => {});
parser.setCsiHandler('s', (params, collect) => {});
parser.setCsiHandler('u', (params, collect) => {});
parser.setCsiHandler({final: '@'}, params => {});
parser.setCsiHandler({final: 'A'}, params => {});
parser.setCsiHandler({final: 'B'}, params => {});
parser.setCsiHandler({final: 'C'}, params => {});
parser.setCsiHandler({final: 'D'}, params => {});
parser.setCsiHandler({final: 'E'}, params => {});
parser.setCsiHandler({final: 'F'}, params => {});
parser.setCsiHandler({final: 'G'}, params => {});
parser.setCsiHandler({final: 'H'}, params => {});
parser.setCsiHandler({final: 'I'}, params => {});
parser.setCsiHandler({final: 'J'}, params => {});
parser.setCsiHandler({final: 'K'}, params => {});
parser.setCsiHandler({final: 'L'}, params => {});
parser.setCsiHandler({final: 'M'}, params => {});
parser.setCsiHandler({final: 'P'}, params => {});
parser.setCsiHandler({final: 'S'}, params => {});
parser.setCsiHandler({final: 'T'}, params => {});
parser.setCsiHandler({final: 'X'}, params => {});
parser.setCsiHandler({final: 'Z'}, params => {});
parser.setCsiHandler({final: '`'}, params => {});
parser.setCsiHandler({final: 'a'}, params => {});
parser.setCsiHandler({final: 'b'}, params => {});
parser.setCsiHandler({final: 'c'}, params => {});
parser.setCsiHandler({final: 'd'}, params => {});
parser.setCsiHandler({final: 'e'}, params => {});
parser.setCsiHandler({final: 'f'}, params => {});
parser.setCsiHandler({final: 'g'}, params => {});
parser.setCsiHandler({final: 'h'}, params => {});
parser.setCsiHandler({final: 'l'}, params => {});
parser.setCsiHandler({final: 'm'}, params => {});
parser.setCsiHandler({final: 'n'}, params => {});
parser.setCsiHandler({final: 'p'}, params => {});
parser.setCsiHandler({final: 'q'}, params => {});
parser.setCsiHandler({final: 'r'}, params => {});
parser.setCsiHandler({final: 's'}, params => {});
parser.setCsiHandler({final: 'u'}, params => {});
parser.setExecuteHandler(C0.BEL, () => {});
parser.setExecuteHandler(C0.LF, () => {});
parser.setExecuteHandler(C0.VT, () => {});
@@ -82,23 +82,23 @@ perfContext('Parser throughput - 50MB data', () => {
parser.setExecuteHandler(C1.HTS, () => {});
parser.setOscHandler(0, new OscHandlerFactory((data) => {}));
parser.setOscHandler(2, new OscHandlerFactory((data) => {}));
parser.setEscHandler('7', () => {});
parser.setEscHandler('8', () => {});
parser.setEscHandler('D', () => {});
parser.setEscHandler('E', () => {});
parser.setEscHandler('H', () => {});
parser.setEscHandler('M', () => {});
parser.setEscHandler('=', () => {});
parser.setEscHandler('>', () => {});
parser.setEscHandler('c', () => {});
parser.setEscHandler('n', () => {});
parser.setEscHandler('o', () => {});
parser.setEscHandler('|', () => {});
parser.setEscHandler('}', () => {});
parser.setEscHandler('~', () => {});
parser.setEscHandler('%@', () => {});
parser.setEscHandler('%G', () => {});
parser.setDcsHandler('q', new DcsHandler());
parser.setEscHandler({final: '7'}, () => {});
parser.setEscHandler({final: '8'}, () => {});
parser.setEscHandler({final: 'D'}, () => {});
parser.setEscHandler({final: 'E'}, () => {});
parser.setEscHandler({final: 'H'}, () => {});
parser.setEscHandler({final: 'M'}, () => {});
parser.setEscHandler({final: '='}, () => {});
parser.setEscHandler({final: '>'}, () => {});
parser.setEscHandler({final: 'c'}, () => {});
parser.setEscHandler({final: 'n'}, () => {});
parser.setEscHandler({final: 'o'}, () => {});
parser.setEscHandler({final: '|'}, () => {});
parser.setEscHandler({final: '}'}, () => {});
parser.setEscHandler({final: '~'}, () => {});
parser.setEscHandler({intermediates: '%', final: '@'}, () => {});
parser.setEscHandler({intermediates: '%', final: 'G'}, () => {});
parser.setDcsHandler({final: 'q'}, new DcsHandler());
});
perfContext('PRINT - a', () => {
+27 -10
View File
@@ -502,8 +502,8 @@ declare module 'xterm' {
/**
* Adds a handler for CSI escape sequences.
* @param flag The flag should be one-character string, which specifies the
* final character (e.g "m" for SGR) of the CSI sequence.
* @param id Specifies the function identifier under which the callback gets registered,
* e.g. {final: 'm'} for SGR.
* @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
@@ -513,12 +513,12 @@ 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 | number[])[], collect: string) => boolean): IDisposable;
addCsiHandler(id: IFunctionIdentifier, callback: (params: (number | number[])[]) => boolean): IDisposable;
/**
* Adds a handler for DCS escape sequences.
* @param collect Should be a string, which specifies the collect and the
* final character (e.g "$q" for DECRQSS) of the DCS sequence.
* @param id Specifies the function identifier under which the callback gets registered,
* e.g. {intermediates: '$' final: 'q'} for DECRQSS.
* @param callback The function to handle the escape sequence. Note that the
* function will only be called once if the sequence finished sucessfully.
* There is currently no way to intercept smaller data chunks, those will be stored up
@@ -531,20 +531,19 @@ declare module 'xterm' {
* The most recently-added handler is tried first.
* @return An IDisposable you can call to remove this handler.
*/
addDcsHandler(collect: string, callback: (param: (number | number[])[], data: string) => boolean): IDisposable;
addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: (number | number[])[]) => boolean): IDisposable;
/**
* Adds a handler for ESC escape sequences.
* @param collect Should be a string, which specifies the collect and the
* final character (e.g "%G" for default charset selection)
* of the ESC sequence.
* @param id Specifies the function identifier under which the callback gets registered,
* e.g. {intermediates: '%' final: 'G'} for default charset selection.
* @param callback The function to handle the escape sequence.
* Return true if the sequence was handled; false if
* we should try a previous handler (set by addEscHandler or setEscHandler).
* The most recently-added handler is tried first.
* @return An IDisposable you can call to remove this handler.
*/
addEscHandler(collect: string, handler: () => boolean): IDisposable;
addEscHandler(id: IFunctionIdentifier, handler: () => boolean): IDisposable;
/**
* Adds a handler for OSC escape sequences.
@@ -987,4 +986,22 @@ declare module 'xterm' {
*/
readonly width: number;
}
/**
* Data type to register a CSI, DCS or ESC callback in the parser.
*/
export interface IFunctionIdentifier {
/**
* Optional prefix byte, must be in range \x3c .. \x3f.
*/
prefix?: string;
/**
* Optional intermediate bytes, must be in range \x20 .. \x2f.
*/
intermediates?: string;
/**
* Final byte, must be in range \x40 .. \x7e (\x30 .. \x7e for ESC).
*/
final: string;
}
}