mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
async impl for DCS, DcsParser tests
This commit is contained in:
@@ -252,3 +252,208 @@ describe('DcsParser', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
class TestHandlerAsync implements IDcsHandler {
|
||||
constructor(public output: any[], public msg: string, public returnFalse: boolean = false) {}
|
||||
public hook(params: IParams): void {
|
||||
this.output.push([this.msg, 'HOOK', params.toArray()]);
|
||||
}
|
||||
public put(data: Uint32Array, start: number, end: number): void {
|
||||
this.output.push([this.msg, 'PUT', utf32ToString(data, start, end)]);
|
||||
}
|
||||
public async unhook(success: boolean): Promise<boolean> {
|
||||
// simple sleep to check in tests whether ordering gets messed up
|
||||
await new Promise(res => setTimeout(res, 20));
|
||||
this.output.push([this.msg, 'UNHOOK', success]);
|
||||
if (this.returnFalse) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
async function unhookP(parser: DcsParser, success: boolean): Promise<void> {
|
||||
let result: void | Promise<boolean>;
|
||||
let prev: boolean | undefined;
|
||||
while (result = parser.unhook(success, prev)) {
|
||||
prev = await result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
describe('DcsParser - async tests', () => {
|
||||
let parser: DcsParser;
|
||||
let reports: any[] = [];
|
||||
beforeEach(() => {
|
||||
reports = [];
|
||||
parser = new DcsParser();
|
||||
parser.setHandlerFallback((id, action, data) => {
|
||||
if (action === 'HOOK') {
|
||||
data = data.toArray();
|
||||
}
|
||||
reports.push([id, action, data]);
|
||||
});
|
||||
});
|
||||
describe('sync and async mixed', () => {
|
||||
describe('sync | async | sync', () => {
|
||||
it('first should run, cleanup action for others', async () => {
|
||||
parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 's1', false));
|
||||
parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandlerAsync(reports, 'a1', false));
|
||||
parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 's2', 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!');
|
||||
parser.put(data, 0, data.length);
|
||||
await unhookP(parser, true);
|
||||
assert.deepEqual(reports, [
|
||||
// messages from TestHandler
|
||||
['s2', 'HOOK', [1, 2, 3]],
|
||||
['a1', 'HOOK', [1, 2, 3]],
|
||||
['s1', 'HOOK', [1, 2, 3]],
|
||||
['s2', 'PUT', 'Here comes'],
|
||||
['a1', 'PUT', 'Here comes'],
|
||||
['s1', 'PUT', 'Here comes'],
|
||||
['s2', 'PUT', 'the mouse!'],
|
||||
['a1', 'PUT', 'the mouse!'],
|
||||
['s1', 'PUT', 'the mouse!'],
|
||||
['s2', 'UNHOOK', true],
|
||||
['a1', 'UNHOOK', false], // important: a1 before s1
|
||||
['s1', 'UNHOOK', false]
|
||||
]);
|
||||
});
|
||||
it('all should run', async () => {
|
||||
parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 's1', true));
|
||||
parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandlerAsync(reports, 'a1', true));
|
||||
parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 's2', 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);
|
||||
await unhookP(parser, true);
|
||||
assert.deepEqual(reports, [
|
||||
// messages from TestHandler
|
||||
['s2', 'HOOK', [1, 2, 3]],
|
||||
['a1', 'HOOK', [1, 2, 3]],
|
||||
['s1', 'HOOK', [1, 2, 3]],
|
||||
['s2', 'PUT', 'Here comes'],
|
||||
['a1', 'PUT', 'Here comes'],
|
||||
['s1', 'PUT', 'Here comes'],
|
||||
['s2', 'PUT', 'the mouse!'],
|
||||
['a1', 'PUT', 'the mouse!'],
|
||||
['s1', 'PUT', 'the mouse!'],
|
||||
['s2', 'UNHOOK', true],
|
||||
['a1', 'UNHOOK', true], // important: a1 before s1
|
||||
['s1', 'UNHOOK', true]
|
||||
]);
|
||||
});
|
||||
});
|
||||
describe('async | sync | async', () => {
|
||||
it('first should run, cleanup action for others', async () => {
|
||||
parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandlerAsync(reports, 'a1', false));
|
||||
parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 's1', false));
|
||||
parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandlerAsync(reports, 'a2', 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!');
|
||||
parser.put(data, 0, data.length);
|
||||
await unhookP(parser, true);
|
||||
assert.deepEqual(reports, [
|
||||
// messages from TestHandler
|
||||
['a2', 'HOOK', [1, 2, 3]],
|
||||
['s1', 'HOOK', [1, 2, 3]],
|
||||
['a1', 'HOOK', [1, 2, 3]],
|
||||
['a2', 'PUT', 'Here comes'],
|
||||
['s1', 'PUT', 'Here comes'],
|
||||
['a1', 'PUT', 'Here comes'],
|
||||
['a2', 'PUT', 'the mouse!'],
|
||||
['s1', 'PUT', 'the mouse!'],
|
||||
['a1', 'PUT', 'the mouse!'],
|
||||
['a2', 'UNHOOK', true],
|
||||
['s1', 'UNHOOK', false], // important: s1 between a2 .. a1
|
||||
['a1', 'UNHOOK', false]
|
||||
]);
|
||||
});
|
||||
it('all should run', async () => {
|
||||
parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandlerAsync(reports, 'a1', true));
|
||||
parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandler(reports, 's1', true));
|
||||
parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new TestHandlerAsync(reports, 'a2', 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);
|
||||
await unhookP(parser, true);
|
||||
assert.deepEqual(reports, [
|
||||
// messages from TestHandler
|
||||
['a2', 'HOOK', [1, 2, 3]],
|
||||
['s1', 'HOOK', [1, 2, 3]],
|
||||
['a1', 'HOOK', [1, 2, 3]],
|
||||
['a2', 'PUT', 'Here comes'],
|
||||
['s1', 'PUT', 'Here comes'],
|
||||
['a1', 'PUT', 'Here comes'],
|
||||
['a2', 'PUT', 'the mouse!'],
|
||||
['s1', 'PUT', 'the mouse!'],
|
||||
['a1', 'PUT', 'the mouse!'],
|
||||
['a2', 'UNHOOK', true],
|
||||
['s1', 'UNHOOK', true], // important: s1 between a2 .. a1
|
||||
['a1', 'UNHOOK', true]
|
||||
]);
|
||||
});
|
||||
});
|
||||
describe('DcsHandlerFactory', () => {
|
||||
it('should be called once on end(true)', async () => {
|
||||
parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler(async (data, params) => { reports.push([params.toArray(), data]); return 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);
|
||||
await unhookP(parser, true);
|
||||
assert.deepEqual(reports, [[[1, 2, 3], 'Here comes the mouse!']]);
|
||||
});
|
||||
it('should not be called on end(false)', async () => {
|
||||
parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler(async (data, params) => { reports.push([params.toArray(), data]); return 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);
|
||||
await unhookP(parser, false);
|
||||
assert.deepEqual(reports, []);
|
||||
});
|
||||
it('should be disposable', async () => {
|
||||
parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler(async (data, params) => { reports.push(['one', params.toArray(), data]); return true; }));
|
||||
const dispo = parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler(async (data, params) => { reports.push(['two', params.toArray(), data]); return 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);
|
||||
await unhookP(parser, true);
|
||||
assert.deepEqual(reports, [['two', [1, 2, 3], 'Here comes the mouse!']]);
|
||||
dispo.dispose();
|
||||
parser.hook(identifier({intermediates: '+', final: 'p'}), Params.fromArray([1, 2, 3]));
|
||||
data = toUtf32('some other');
|
||||
parser.put(data, 0, data.length);
|
||||
data = toUtf32(' data');
|
||||
parser.put(data, 0, data.length);
|
||||
await unhookP(parser, true);
|
||||
assert.deepEqual(reports, [['two', [1, 2, 3], 'Here comes the mouse!'], ['one', [1, 2, 3], 'some other data']]);
|
||||
});
|
||||
it('should respect return false', async () => {
|
||||
parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler(async (data, params) => { reports.push(['one', params.toArray(), data]); return true; }));
|
||||
parser.registerHandler(identifier({intermediates: '+', final: 'p'}), new DcsHandler(async (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!');
|
||||
parser.put(data, 0, data.length);
|
||||
await unhookP(parser, true);
|
||||
assert.deepEqual(reports, [['two', [1, 2, 3], 'Here comes the mouse!'], ['one', [1, 2, 3], 'Here comes the mouse!']]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,11 +15,11 @@ export class DcsParser implements IDcsParser {
|
||||
private _handlers: IHandlerCollection<IDcsHandler> = Object.create(null);
|
||||
private _active: IDcsHandler[] = EMPTY_HANDLERS;
|
||||
private _ident: number = 0;
|
||||
private _handlerFb: DcsFallbackHandlerType = () => {};
|
||||
private _handlerFb: DcsFallbackHandlerType = () => { };
|
||||
|
||||
public dispose(): void {
|
||||
this._handlers = Object.create(null);
|
||||
this._handlerFb = () => {};
|
||||
this._handlerFb = () => { };
|
||||
this._active = EMPTY_HANDLERS;
|
||||
}
|
||||
|
||||
@@ -79,20 +79,46 @@ export class DcsParser implements IDcsParser {
|
||||
}
|
||||
}
|
||||
|
||||
public unhook(success: boolean): void {
|
||||
private _stack = {
|
||||
paused: false,
|
||||
loopPosition: 0,
|
||||
fallThrough: false
|
||||
};
|
||||
public unhook(success: boolean, promiseResult?: boolean): void | Promise<boolean> {
|
||||
if (!this._active.length) {
|
||||
this._handlerFb(this._ident, 'UNHOOK', success);
|
||||
} else {
|
||||
let handlerResult: any = false;
|
||||
let j = this._active.length - 1;
|
||||
for (; j >= 0; j--) {
|
||||
if (this._active[j].unhook(success)) {
|
||||
break;
|
||||
}
|
||||
let fallThrough = false;
|
||||
if (this._stack.paused) {
|
||||
j = this._stack.loopPosition - 1;
|
||||
handlerResult = promiseResult;
|
||||
fallThrough = this._stack.fallThrough;
|
||||
this._stack.paused = false;
|
||||
}
|
||||
j--;
|
||||
// cleanup left over handlers
|
||||
if (!fallThrough && handlerResult === false) {
|
||||
for (; j >= 0; j--) {
|
||||
if ((handlerResult = this._active[j].unhook(success)) !== false) {
|
||||
if (handlerResult instanceof Promise) {
|
||||
this._stack.paused = true;
|
||||
this._stack.loopPosition = j;
|
||||
this._stack.fallThrough = false;
|
||||
return handlerResult;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
j--;
|
||||
}
|
||||
// cleanup left over handlers (fallThrough for async)
|
||||
for (; j >= 0; j--) {
|
||||
this._active[j].unhook(false);
|
||||
if ((handlerResult = this._active[j].unhook(false)) instanceof Promise) {
|
||||
this._stack.paused = true;
|
||||
this._stack.loopPosition = j;
|
||||
this._stack.fallThrough = true;
|
||||
return handlerResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
this._active = EMPTY_HANDLERS;
|
||||
@@ -113,7 +139,7 @@ export class DcsHandler implements IDcsHandler {
|
||||
private _params: IParams = EMPTY_PARAMS;
|
||||
private _hitLimit: boolean = false;
|
||||
|
||||
constructor(private _handler: (data: string, params: IParams) => boolean) {}
|
||||
constructor(private _handler: (data: string, params: IParams) => boolean | Promise<boolean>) { }
|
||||
|
||||
public hook(params: IParams): void {
|
||||
// since we need to preserve params until `unhook`, we have to clone it
|
||||
@@ -136,12 +162,21 @@ export class DcsHandler implements IDcsHandler {
|
||||
}
|
||||
}
|
||||
|
||||
public unhook(success: boolean): boolean {
|
||||
let ret = false;
|
||||
public unhook(success: boolean): boolean | Promise<boolean> {
|
||||
let ret: boolean | Promise<boolean> = false;
|
||||
if (this._hitLimit) {
|
||||
ret = false;
|
||||
} else if (success) {
|
||||
ret = this._handler(this._data, this._params);
|
||||
if ((ret = this._handler(this._data, this._params)) instanceof Promise) {
|
||||
// FIXME: should this be behind a catch rule?
|
||||
return ret.then(res => {
|
||||
// cleanup handler state late
|
||||
this._params = EMPTY_PARAMS;
|
||||
this._data = '';
|
||||
this._hitLimit = false;
|
||||
return res;
|
||||
});
|
||||
}
|
||||
}
|
||||
this._params = EMPTY_PARAMS;
|
||||
this._data = '';
|
||||
|
||||
@@ -239,7 +239,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
|
||||
// handler lookup containers
|
||||
protected _printHandler: PrintHandlerType;
|
||||
protected _executeHandlers: {[flag: number]: ExecuteHandlerType};
|
||||
protected _executeHandlers: { [flag: number]: ExecuteHandlerType };
|
||||
protected _csiHandlers: IHandlerCollection<CsiHandlerType>;
|
||||
protected _escHandlers: IHandlerCollection<EscHandlerType>;
|
||||
protected _oscParser: IOscParser;
|
||||
@@ -280,7 +280,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
this._errorHandler = this._errorHandlerFb;
|
||||
|
||||
// swallow 7bit ST (ESC+\)
|
||||
this.registerEscHandler({final: '\\'}, () => true);
|
||||
this.registerEscHandler({ final: '\\' }, () => true);
|
||||
}
|
||||
|
||||
protected _identifier(id: IFunctionIdentifier, finalRange: number[] = [0x40, 0x7e]): number {
|
||||
@@ -452,8 +452,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
handlers: ResumableHandlersType,
|
||||
handlerPos: number,
|
||||
transition: number,
|
||||
chunkPos: number): void
|
||||
{
|
||||
chunkPos: number): void {
|
||||
this._parseStack.state = state;
|
||||
this._parseStack.handlers = handlers;
|
||||
this._parseStack.handlerPos = handlerPos;
|
||||
@@ -534,10 +533,11 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
// - handlers are not exhausted yet
|
||||
// FIXME: removing handlers from within a handler of the same sequence
|
||||
// is not supported atm (also true for sync handlers)!!
|
||||
if (promiseResult === false && handlerPos > -1) {
|
||||
const handlers = this._parseStack.handlers;
|
||||
switch (this._parseStack.state) {
|
||||
case ParserStackType.CSI:
|
||||
let handlers: ResumableHandlersType;
|
||||
switch (this._parseStack.state) {
|
||||
case ParserStackType.CSI:
|
||||
if (promiseResult === false && handlerPos > -1) {
|
||||
handlers = this._parseStack.handlers;
|
||||
for (; handlerPos >= 0; handlerPos--) {
|
||||
if ((handlerResult = (handlers as CsiHandlerType[])[handlerPos](this._params)) !== false) {
|
||||
if (handlerResult instanceof Promise) {
|
||||
@@ -547,8 +547,11 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ParserStackType.ESC:
|
||||
}
|
||||
break;
|
||||
case ParserStackType.ESC:
|
||||
if (promiseResult === false && handlerPos > -1) {
|
||||
handlers = this._parseStack.handlers;
|
||||
for (; handlerPos >= 0; handlerPos--) {
|
||||
if ((handlerResult = (handlers as EscHandlerType[])[handlerPos]()) !== false) {
|
||||
if (handlerResult instanceof Promise) {
|
||||
@@ -558,8 +561,21 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ParserStackType.DCS:
|
||||
code = data[this._parseStack.chunkPos];
|
||||
if (handlerResult = this._dcsParser.unhook(code !== 0x18 && code !== 0x1a)) {
|
||||
return handlerResult;
|
||||
}
|
||||
if (code === 0x1b) this._parseStack.transition |= ParserState.ESCAPE;
|
||||
this._params.reset();
|
||||
this._params.addParam(0); // ZDM
|
||||
this._collect = 0;
|
||||
break;
|
||||
case ParserStackType.OSC:
|
||||
// TODO
|
||||
break;
|
||||
}
|
||||
// cleanup before continuing with the main loop
|
||||
this._parseStack.state = ParserStackType.NONE;
|
||||
@@ -700,7 +716,10 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
}
|
||||
break;
|
||||
case ParserAction.DCS_UNHOOK:
|
||||
this._dcsParser.unhook(code !== 0x18 && code !== 0x1a);
|
||||
if (handlerResult = this._dcsParser.unhook(code !== 0x18 && code !== 0x1a)) {
|
||||
this._parseStack.state = ParserStackType.DCS;
|
||||
return handlerResult;
|
||||
}
|
||||
if (code === 0x1b) transition |= ParserState.ESCAPE;
|
||||
this._params.reset();
|
||||
this._params.addParam(0); // ZDM
|
||||
|
||||
Vendored
+3
-3
@@ -5,7 +5,7 @@
|
||||
|
||||
import { IDisposable } from 'common/Types';
|
||||
import { ParserState } from 'common/parser/Constants';
|
||||
import { OscParser } from 'common/parser/OscParser';
|
||||
|
||||
|
||||
/** sequence params serialized to js arrays */
|
||||
export type ParamsArray = (number | number[])[];
|
||||
@@ -94,7 +94,7 @@ export interface IDcsHandler {
|
||||
* execution of the command should depend on `success`.
|
||||
* To save memory also cleanup data structures here.
|
||||
*/
|
||||
unhook(success: boolean): boolean;
|
||||
unhook(success: boolean): boolean | Promise<boolean>;
|
||||
}
|
||||
export type DcsFallbackHandlerType = (ident: number, action: 'HOOK' | 'PUT' | 'UNHOOK', payload?: any) => void;
|
||||
|
||||
@@ -219,7 +219,7 @@ export interface IOscParser extends ISubParser<IOscHandler, OscFallbackHandlerTy
|
||||
|
||||
export interface IDcsParser extends ISubParser<IDcsHandler, DcsFallbackHandlerType> {
|
||||
hook(ident: number, params: IParams): void;
|
||||
unhook(success: boolean): void;
|
||||
unhook(success: boolean, promiseResult?: boolean): void | Promise<boolean>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user